これからテストを書き始めたい人のための Rails+RSpec+Spork+FactoryGirl チュートリアル（その２）


その１が長くなってしまったので、つづきです。

## Spork を使う
テスト実行時の時間を短縮するため、Spork を導入します。
まず、gem をインストールします。

```
$ vim Gemfile
```

```ruby:Gemfile
group :development, :test do
  gem 'rspec-rails', '2.10.1'
  gem 'spork', '0.9.2'
end
```

```
$ bundle
Fetching source index for https://rubygems.org/
Using rake (0.9.2.2) 
Using i18n (0.6.0) 
Using multi_json (1.3.6) 
Using activesupport (3.2.6) 
Using builder (3.0.0) 
Using activemodel (3.2.6) 
Using erubis (2.7.0) 
Using journey (1.0.4) 
Using rack (1.4.1) 
Using rack-cache (1.2) 
Using rack-test (0.6.1) 
Using hike (1.2.1) 
Using tilt (1.3.3) 
Using sprockets (2.1.3) 
Using actionpack (3.2.6) 
Using mime-types (1.18) 
Using polyglot (0.3.3) 
Using treetop (1.4.10) 
Using mail (2.4.4) 
Using actionmailer (3.2.6) 
Using arel (3.0.2) 
Using tzinfo (0.3.33) 
Using activerecord (3.2.6) 
Using activeresource (3.2.6) 
Using bundler (1.0.21) 
Using coffee-script-source (1.3.3) 
Using execjs (1.4.0) 
Using coffee-script (2.2.0) 
Using rack-ssl (1.3.2) 
Using json (1.7.3) 
Using rdoc (3.12) 
Using thor (0.15.2) 
Using railties (3.2.6) 
Using coffee-rails (3.2.2) 
Using diff-lcs (1.1.3) 
Using jquery-rails (2.0.2) 
Using mysql2 (0.3.11) 
Using rails (3.2.6) 
Using rspec-core (2.10.1) 
Using rspec-expectations (2.10.0) 
Using rspec-mocks (2.10.1) 
Using rspec (2.10.0) 
Using rspec-rails (2.10.1) 
Using sass (3.1.19) 
Using sass-rails (3.2.5) 
Installing spork (0.9.2) 
Using uglifier (1.2.4) 
Your bundle is complete! It was installed into ./vendor/bundle
```

次に、Spork の初期設定を行います。

```
$ bundle exec spork --bootstrap
```

spec/spec_helper.rb に Spork 実行用のコードが追記されますので、RSpec のテストが Spork によって実行されるよう修正します。
下記のように、もともとあった RSpec の設定を Spork.prefork ブロックの中に移動します。

```
$ vim spec/spec_helper.rb
```

```ruby:spec/spec_helper.rb
require 'rubygems'
require 'spork'
#uncomment the following line to use spork with the debugger
#require 'spork/ext/ruby-debug'

Spork.prefork do
  # Loading more in this block will cause your tests to run faster. However,
  # if you change any configuration or code from libraries loaded here, you'll
  # need to restart spork for it take effect.

  # This file is copied to spec/ when you run 'rails generate rspec:install'
  ENV["RAILS_ENV"] ||= 'test'
  require File.expand_path("../../config/environment", __FILE__)
  require 'rspec/rails'
  require 'rspec/autorun'

  # Requires supporting ruby files with custom matchers and macros, etc,
  # in spec/support/ and its subdirectories.
  Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}

  RSpec.configure do |config|
    # ## Mock Framework
    #
    # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
    #
    # config.mock_with :mocha
    # config.mock_with :flexmock
    # config.mock_with :rr

    # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
    config.fixture_path = "#{::Rails.root}/spec/fixtures"

    # If you're not using ActiveRecord, or you'd prefer not to run each of your
    # examples within a transaction, remove the following line or assign false
    # instead of true.
    config.use_transactional_fixtures = true

    # If true, the base class of anonymous controllers will be inferred
    # automatically. This will be the default behavior in future versions of
    # rspec-rails.
    config.infer_base_class_for_anonymous_controllers = false
  end
end

Spork.each_run do
  # This code will be run each time you run your specs.

end
```

Spork が実行できるよう、.rspec に --drb オプションを追記します。

```
$ vim .rspec
```

```ruby:.rspec
--colour
--drb
```

## Spork を使ってテストを実行
Spork を別コンソールで起動しておきます。

```
$ rvm 1.9.2-p290@rails32
$ cd ~/projects/blog
$ bundle exec spork
Using RSpec
Preloading Rails environment
Loading Spork.prefork block...
Spork is ready and listening on 8989!
```

RSpec のテストを実行します。

```
$ bundle exec rspec spec
..............................

Finished in 0.88647 seconds
30 examples, 0 failures
```

Spork にてあらかじめ Rails のコードがロードされるため、テスト実行までの時間が短くなります。

## Fixture を使う
テストデータを使ったテストを書いてみます。
まず、テストデータを用意する前にテストコードを実装します。

```
$ vim spec/models/article_spec.rb
```

```ruby:spec/models/article_spec.rb
# coding: utf-8

require 'spec_helper'

describe Article do
  # 追記ここから
  fixtures(:all)

  describe '.all' do
    subject { Article.all }
    it { should have(1).items }
  end
  # 追記ここまで

  describe '.new' do
    context 'given valid attributes' do
      subject { Article.new(:title => 'a', :content => 'a') }
      it { should be_valid }
    end

    context 'given null title' do
      subject { Article.new(:content => 'a') }
      it { should have(1).errors_on(:title) }
    end
  end
end
```

テストを実行し、「失敗」することを確認します。

```
$ bundle exec rspec spec
................F..............

Failures:

  1) Article.all 
     Failure/Error: it { should have(1).items }
       expected 1 items, got 0
     # ./spec/models/article_spec.rb:10:in `block (3 levels) in <top (required)>'

Finished in 1.3 seconds
31 examples, 1 failure

Failed examples:

rspec ./spec/models/article_spec.rb:10 # Article.all 
```

テストデータを用意します。

```
$ mkdir spec/fixtures
$ vim spec/fixtures/articles.yml
```

```spec/fixtures/articles.yml
article_001:
  title: 'MyString'
  content: 'MyText'
```

テストを実行します。

```
$ bundle exec rspec spec
...............................

Finished in 0.82728 seconds
31 examples, 0 failures
```

テスト実行時に Fixture がテスト DB に読み込まれるようになりましたので、結果は「成功」となります。

## Fixture の代わりに FactoryGirl を使う
Fixture Replacement の代表格、FactoryGirl を使ってみます。
テストデータが DB に残ってしまい、意図しないテスト結果となることを防ぐため、DatabaseCleaner も併せてインストールします。

```
$ vim Gemfile
```

```ruby:Gemfile
group :development, :test do
  gem 'rspec-rails', '2.10.1'
  gem 'spork', '0.9.2'
  gem 'factory_girl_rails', '3.4.0'
  gem 'database_cleaner', '0.8.0'
end
```

```
$ bundle
Fetching source index for https://rubygems.org/
Using rake (0.9.2.2) 
Using i18n (0.6.0) 
Using multi_json (1.3.6) 
Using activesupport (3.2.6) 
Using builder (3.0.0) 
Using activemodel (3.2.6) 
Using erubis (2.7.0) 
Using journey (1.0.4) 
Using rack (1.4.1) 
Using rack-cache (1.2) 
Using rack-test (0.6.1) 
Using hike (1.2.1) 
Using tilt (1.3.3) 
Using sprockets (2.1.3) 
Using actionpack (3.2.6) 
Using mime-types (1.18) 
Using polyglot (0.3.3) 
Using treetop (1.4.10) 
Using mail (2.4.4) 
Using actionmailer (3.2.6) 
Using arel (3.0.2) 
Using tzinfo (0.3.33) 
Using activerecord (3.2.6) 
Using activeresource (3.2.6) 
Using bundler (1.0.21) 
Using coffee-script-source (1.3.3) 
Using execjs (1.4.0) 
Using coffee-script (2.2.0) 
Using rack-ssl (1.3.2) 
Using json (1.7.3) 
Using rdoc (3.12) 
Using thor (0.15.2) 
Using railties (3.2.6) 
Using coffee-rails (3.2.2) 
Installing database_cleaner (0.8.0) 
Using diff-lcs (1.1.3) 
Installing factory_girl (3.4.0) 
Installing factory_girl_rails (3.4.0) 
Using jquery-rails (2.0.2) 
Using mysql2 (0.3.11) 
Using rails (3.2.6) 
Using rspec-core (2.10.1) 
Using rspec-expectations (2.10.0) 
Using rspec-mocks (2.10.1) 
Using rspec (2.10.0) 
Using rspec-rails (2.10.1) 
Using sass (3.1.19) 
Using sass-rails (3.2.5) 
Using spork (0.9.2) 
Using uglifier (1.2.4) 
Your bundle is complete! It was installed into ./vendor/bundle
```

## FactoryGirl および DatabaseCleaner を設定
一旦 Spork を止め、DatabaseCleaner によりテスト実行毎にテスト DB のデータが削除されるようにします。
また、Factory が追加された際に Spork を再起動しなくても読み込めるよう設定します。

```
$ vim spec/spec_helper.rb
```

```spec/spec_helper.rb
RSpec.configure do |config|
  # ## Mock Framework
  #
  # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
  #
  # config.mock_with :mocha
  # config.mock_with :flexmock
  # config.mock_with :rr

  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
  config.fixture_path = "#{::Rails.root}/spec/fixtures"

  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.
  config.use_transactional_fixtures = true

  # If true, the base class of anonymous controllers will be inferred
  # automatically. This will be the default behavior in future versions of
  # rspec-rails.
  config.infer_base_class_for_anonymous_controllers = false

  # 追記ここから
  config.before(:suite) do
    DatabaseCleaner.strategy = :truncation
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end
  # 追記ここまで
end


Spork.each_run do
  # This code will be run each time you run your specs.

  FactoryGirl.reload # 追記
end
```

再び Spork を起動します。

```
$ bundle exec spork
```

## Factory を使用してテストを実行

テストコードでは Fixture を読み込まないようにし、FactoryGirl を使うようにします。

```
$ vim spec/models/article_spec.rb
```

```ruby:spec/models/article_spec.rb
# coding: utf-8

require 'spec_helper'

describe Article do
  # fixtures(:all) を削除

  describe '.all' do
    # 追記ここから
    before do
      FactoryGirl.create(:article)
    end
    # 追記ここまで

    subject { Article.all }
    it { should have(1).items }
  end

  describe '.new' do
    context 'given valid attributes' do
      subject { Article.new(:title => 'a', :content => 'a') }
      it { should be_valid }
    end

    context 'given null title' do
      subject { Article.new(:content => 'a') }
      it { should have(1).errors_on(:title) }
    end
  end
end
```

テストを実行し、「失敗」することを確認します。

```
$ bundle exec rspec spec
................F..............

Failures:

  1) Article.all 
     Failure/Error: FactoryGirl.create(:article)
     ArgumentError:
       Factory not registered: article
     # ./spec/models/article_spec.rb:8:in `block (3 levels) in <top (required)>'

Finished in 1.14 seconds
31 examples, 1 failure

Failed examples:

rspec ./spec/models/article_spec.rb:12 # Article.all
```

Factory を自動生成します。

```
$ bundle exec rails g factory_girl:model article title:string content:text
      create  spec/factories/articles.rb
```

spec/factories 以下に Factory が生成されます。

```spec/factories/articles.rb
# Read about factories at https://github.com/thoughtbot/factory_girl

FactoryGirl.define do
  factory :article do
    title "MyString"
    content "MyText"
  end
end
```

再度、テストを実行します。

```
$ bundle exec rspec spec
...............................

Finished in 1.23 seconds
31 examples, 0 failures
```

Fixture の代わりに FactoryGirl を使ってテストを実行することができました。

## RSpec 実行時のオプションを指定（おまけ）
RSpec では、テスト実行時の見た目を変更することができます。

```
$ vim .rspec
```

```ruby:.rspec
--colour
--drb
--format d
```

テストを実行すると、下記のように表示されます。

```
$ bundle exec rspec spec

ArticlesController
  GET index
    assigns all articles as @articles
  GET show
    assigns the requested article as @article
  GET new
    assigns a new article as @article
  GET edit
    assigns the requested article as @article
  POST create
    with valid params
      creates a new Article
      assigns a newly created article as @article
      redirects to the created article
    with invalid params
      assigns a newly created but unsaved article as @article
      re-renders the 'new' template
  PUT update
    with valid params
      updates the requested article
      assigns the requested article as @article
      redirects to the article
    with invalid params
      assigns the article as @article
      re-renders the 'edit' template
  DELETE destroy
    destroys the requested article
    redirects to the articles list

Article
  .all
    should have 1 items
  .new
    given valid attributes
      should be valid
    given null title
      should have 1 errors on :title

Articles
  GET /articles
    works! (now write some real specs)

ArticlesController
  routing
    routes to #index
    routes to #new
    routes to #show
    routes to #edit
    routes to #create
    routes to #update
    routes to #destroy

articles/edit
  renders the edit article form

articles/index
  renders a list of articles

articles/new
  renders new article form

articles/show
  renders attributes in <p>

Finished in 1.28 seconds
31 examples, 0 failures
```

以上でチュートリアルは終わりです。
本格的にアプリケーションを開発するには足りないところが多すぎますが、まずはテストを書き始めるきっかけになればと思います。