Posted at

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

More than 5 years have passed since last update.

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


Spork を使う

テスト実行時の時間を短縮するため、Spork を導入します。

まず、gem をインストールします。

$ vim Gemfile


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


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


.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


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


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


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


.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

以上でチュートリアルは終わりです。

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