LoginSignup
133

More than 5 years have passed since last update.

[Rails] RSpecによるBDD(振舞駆動開発)の基本 [SporkとGuardも]

Last updated at Posted at 2013-11-11

概要

Ruby on Rails Tutorialのエッセンスを自分なりに整理してみる2

Railsを支える基本概念の整理(RESTfulやリソースなど)
http://qiita.com/kidachi_/items/43e53811c12351915278
の続き。

Ruby on Rails Tutorial(chapter3)
http://railstutorial.jp/chapters/static-pages?version=4.0#sec-spork

下準備

rails new

RSpecを用いるため、test-unitは不要。

$ rails new rspec_sample --skip-test-unit

Gemfileの設定

Gemfile
group :development, :test do
  gem 'sqlite3', '1.3.8'
  gem 'rspec-rails', '2.13.1'
end

group :test do
  gem 'selenium-webdriver', '2.35.1'
  gem 'capybara', '2.1.0'
end

group :production do
  gem 'pg', '0.15.1'
  gem 'rails_12factor', '0.0.2'
end
  • capybara Web のアクセスをシミュレートするヘルパー。 ブラウザやエミュレータ上で想定される操作をrubyで書き、 テストに含めることが出来る

capybara で快適なテスト生活を
http://www.slideshare.net/tricknotes/capybara-introduction

  • pg
    PostgreSQLを扱うためのgem

  • rails_12factor
    HerokuをRailsで動作させるためのgem

アプリケーションの実装

controllerの作成

$ rails g controller <ClassName> <ActionName(任意個数)>
$ rails g controller StaticPages home help --no-test-framework

※RSpec用にtest-unitは除く

ルーティングの自動生成

config/routes.rb
SampleApp::Application.routes.draw do
  get "static_pages/home"
  get "static_pages/help"
  ~
end

/static_pages/home(と/help)というURLに対するGETリクエストを、
StaticPagesコントローラのhome(とhelp)アクションと結びつけている

(おまけ)各種ロールバックの方法

controller

 $ rails g controller FooBars baz quux
//controller名とaction名
 $ rails destroy  controller FooBars baz quux

model


$ rails g model Foo bar:string baz:integer
//model名のみでok
$ rails destroy model Foo

db

$ rake db:migrate
//1つ前の状態に戻す
$ rake db:rollback
//最初の状態に戻す
$ rake db:migrate VERSION=0

※マイグレーションは逐次的に実行され、それぞれのマイグレーションに対してバージョン番号が付与されている。

テスト駆動の開始

結合テスト (request spec) を生成
$ rails g integration_test static_pages
/homeのviewは、'Micro'という文字列(アプリ名)を持っているべき
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "StaticPages" do
  describe "Home page" do
    it "should have the content 'Micro'" do
      visit '/static_pages/home'
      expect(page).to have_content('Micro')
    end
  end
end

RSpec実行
$ bundle exec rspec spec/requests/static_pages_spec.rb
F

Failures:

  1) StaticPages Home page should have the content 'Micro'
     Failure/Error: expect(page).to have_content('Micro')
       expected #has_content?("Micro") to return true, got false
     # ./spec/requests/static_pages_spec.rb:7:in `block (3 levels) in <top (required)>'

Finished in 0.83735 seconds
1 example, 1 failure

Failed examples:

rspec ./spec/requests/static_pages_spec.rb:5 # StaticPages Home page should have the content 'Micro'

Randomized with seed 4199

想定通りRed(失敗)

テストをパスするようにアプリケーションに手を加える。

app/views/static_pages/home.html.erb
<h1>Micro</h1>
<p>
  This is the home page for the
  <a href="http://railstutorial.jp/">Ruby on Rails Tutorial</a>
  sample application.
</p>

再度実行

$ bundle exec rspec spec/requests/static_pages_spec.rb
.

Finished in 0.07615 seconds
1 example, 0 failures

Randomized with seed 11765

Green(成功)。

Guardの導入

Guardとは

あるファイルに変更が加わった時、自動でそれに対するテストを実行する。

Gemfile
~
group :development, :test do
  gem 'sqlite3', '1.3.8'
  gem 'rspec-rails', '2.13.1'
  gem 'guard-rspec', '2.5.0'
end
~

Gemfileに追記の上、bundle install。

$ bundle install
#Guardを初期化し、RSpecと一緒に動作するようにする。
$ bundle exec guard init rspec

結合テストとビューが更新されたら自動的に適切なテストが実行されるようにGuardfileを修正

Guardfile
require 'active_support/inflector'

#失敗したテストが後にパスしたとき、他の余分なテストが実行されないようにする
guard 'rspec', all_after_pass: false do
~
  # Custom Rails Tutorial specs
  watch(%r{^app/controllers/(.+)_(controller)\.rb$})  do |m|
    ["spec/routing/#{m[1]}_routing_spec.rb",
     "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb",
     "spec/acceptance/#{m[1]}_spec.rb",
     (m[1][/_pages/] ? "spec/requests/#{m[1]}_spec.rb" :
                       "spec/requests/#{m[1].singularize}_pages_spec.rb")]
  end
  watch(%r{^app/views/(.+)/}) do |m|
    (m[1][/_pages/] ? "spec/requests/#{m[1]}_spec.rb" :
                      "spec/requests/#{m[1].singularize}_pages_spec.rb")
  end
  watch(%r{^app/controllers/sessions_controller\.rb$}) do |m|
    "spec/requests/authentication_pages_spec.rb"
  end
~
end

Guardの実行。

$ bundle exec guard

Sporkの導入

Sporkとは

Railsにおけるテストの実行時間を短縮してくれるテスト用サーバ。

Gemfile
~
group :development, :test do
  gem 'sqlite3', '1.3.8'
  gem 'rspec-rails', '2.13.1'
  gem 'guard-rspec', '2.5.0'
  gem 'spork-rails', '4.0.0'
  gem 'guard-spork', '1.5.0'
  gem 'childprocess', '0.3.9'
end
~

Gemfileに追記の上、bundle install。

$ bundle install
$ bundle exec spork --bootstrap

--bootstrapを付けた実行により、spec/spec_helper.rbにSpork 実行用のコードが追記される。

RSpec のテストが Spork によって実行されるよう修正。

spec/spec_helper.rb
require 'rubygems'
require 'spork'

Spork.prefork do
  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}

  # Checks for pending migrations before tests are run.
  # If you are not using ActiveRecord, you can remove this line.
  ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)

  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

    # Run specs in random order to surface order dependencies. If you find an
    # order dependency and want to debug it, you can fix the order by providing
    # the seed, which is printed after each run.
    #     --seed 1234
    config.order = "random"
    # Include the Capybara DSL so that specs in spec/requests still work.
    config.include Capybara::DSL
    # Disable the old-style object.should syntax.
    config.expect_with :rspec do |c|
      c.syntax = :expect
    end
  end
end

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

end

spork実行

$ bundle exec spork
#--drbオプションを付ける
$ time bundle exec rspec spec/requests/static_pages_spec.rb --drb
......

6 examples, 0 failures

real    0m2.649s
user    0m1.259s
sys 0m0.258s```

テスト時間の短縮が分かる。

毎回--drbオプションを付けるのは不便なので、.rspecを修正。

--colour
--drb

GuardとSporkの連携

Guardを初期化し、Sporkと一緒に動作するようにする。

$ bundle exec guard init spork

Guardfileの修正。
guardの変数に :cli => --drb を追加し、Guardがコマンドラインから常にSporkサーバーを使うようにする。

Guardfile
guard 'rspec', all_after_pass: false do

guard 'rspec', after_all_pass: false, cli: '--drb' do


以下でGuardとSporkを同時に起動。

$ bundle exec guard

追記1 Guardが動かなくなったら

たまにGuardが反応しなくなることがある。
(エラーメッセージも出ない。)

こういうケースでは、乱暴だがGuardとSporkのプロセスを殺して再起動する。

$ kill -9 `pgrep -f 'spork'`
$ kill -9 `pgrep -f 'guard'`
$ bundle exec guard

※根本原因をご存知の方はアドバイス頂けますと幸いです!

追記2 プロジェクトにTest::Unitが含まれている場合

もしプロジェクトにTest::Unitも含めてしまっていると、(Test::Unit用の設定は行っていないため)以下の様なエラーが出る

18:43:40 - INFO - Starting Spork for RSpec, Test::Unit
Couldn't find a supported test framework that begins with 'testunit'

Supported test frameworks:
( ) Cucumber
(*) RSpec

Legend: ( ) - not detected in project   (*) - detected
Using RSpec, Rails
Preloading Rails environment
Loading Spork.prefork block...
Spork is ready and listening on 8989!
18:44:10 - ERROR - Could not start Spork server for RSpec, Test::Unit after 30 seconds. I will continue waiting for a further 60 seconds.

18:45:10 - ERROR - Could not start Spork server for RSpec, Test::Unit. Make sure you can use it manually first.

その場合は、Guardfileで以下の設定を加えて解決。

-guard 'spork', :cucumber_env => { 'RAILS_ENV' => 'test' }, :rspec_env => { 'RAILS_ENV' => 'test' } do
+guard 'spork', :cucumber_env => { 'RAILS_ENV' => 'test' }, :rspec_env => { 'RAILS_ENV' => 'test' }, :test_unit => false do

以下に続く

Railsを触る際知っていると便利なRubyの基礎 [ブロックとかシンボルとか]
http://qiita.com/kidachi_/items/46a6e49b6306655ccd64

[Ruby基礎] ブロックとProcをちゃんと理解する
http://qiita.com/kidachi_/items/15cfee9ec66804c3afd2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
133