カテゴリー:
Rails
タグ:
 Rspec spork watchr

このエントリーをはてなブックマークに追加
更新日時:
2012年12月11日(火)
作成日時:
2012年12月11日(火)

前の記事 / 次の記事

(Rails4&Spring版は、Rspec+Spring+WatchrでTDDなRails4のテスト環境を構築する に書きました。)

RailsでTDD的な何かをやろうとするとテストの実行速度がアレで全然イケてない感じ。

なので、テストを高速化して、かつファイルに変更があった時点で
自動的にテストが実行されるようなイケてる感じにしたい。

ぐぐるとRspec+Spork+GuardでTDD環境構築してる事例が沢山あるんだけど、
なんでか、自分の環境ではうまく動かない、のでWatchrで自動化。

環境

OS: CentOS6.3, Ruby: 1.9.3p286, Rails: 3.2.9

やること

1.必要なgemのインストール
2.Rspecのセッティング
3.Sporkのセッティング
4.Watchrのセッティング
5.追加の設定
6.参考ページ

1st. 必要なgemのインストール

$ vi Gemfile
group :development, :test do 
  gem 'rspec-rails'
  gem 'spork', '~>1.0rc'
  gem 'watchr'
end

2nd. Rspecのセッティング

$ rails g rspec:install
create  .rspec
create  spec
create  spec/spec_helper.rb

この状態でRailsでモデルやコントローラーを作成した時に自動的にRspecのテストが
生成されるようになっているはずなので適当になにかつくってみる。

$ rails g scaffold user name:string
$ rake db:migrate
$ rake

ちゃんと動いてるっぽければ次へ。

3rd. Sporkのセッティング

$ spork --bootstrap

Using RSpec, Unknown
Bootstrapping /home/appname/spec/spec_helper.rb.
Done. Edit /home/appname/spec/spec_helper.rb now with your favorite text editor and follow the instructions.

このコマンドでSpork上でRspecを動かすための設定が"spec/spec_helper"に追加される。
中身を確認すると、元々あった内容の先頭にSporkの設定が追加されているはずなので、
Spork.preforkの中に元々あった内容を全て移動する。

これを

$ vi 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'
  # need to restart spork for it take effect.

end

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

end

# 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

  # 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"
end

こうする

$ vi 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'
  # 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

    # 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"
  end
end

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

end

Sporkの起動時に一度だけ読み込んで欲しい処理は、Spork.preforkに
Rspecを走らせる度に読み込んで欲しい処理は、Spork.each_runに書く。

これでSporkがRspecと連携するようになっているはずなので試しに動かしてみる。
まず、sporkの起動。

$ spork
Using RSpec, Unknown
Loading Spork.prefork block...
Spork is ready and listening on 8989!

この状態でrspecを走らせればspork上でrspecが動くようになっているはず。

# --drb オプションを付けることでspork上で走るようになる
$ rspec --drb ./spec
..........*..................*

Pending:
  User add some examples to (or delete) /home/appname/spec/models/user_spec.rb
    # No reason given
    # ./spec/models/user_spec.rb:4
  UsersHelper add some examples to (or delete) /home/appname/spec/helpers/users_helper_spec.rb
    # No reason given
    # ./spec/helpers/users_helper_spec.rb:14

Finished in 1.25 seconds
30 examples, 0 failures, 2 pending

Randomized with seed 61875

最初に走らせた時と較べて高速に結果が返って来ているはず。
毎回"--drb"とか指定するのはだるいのでデフォルトで"--drb"してくれるように設定する。

$ vi .rspec
--drb
--color
# --color は実行結果に色を付けるオプション
# 前は--colourでデフォルトでは設定されてなかったはずだけど
# 最近のバージョンだとオプション名が"--color"になってデフォルトで設定されている?

で、

$ rspec ./spec

さっきと同様に高速に結果が返って来ているはず。
うまくいってるっぽかったら次へ。

4th. Watchrのセッティング

ここまででSpork上でRspecを動かして高速にテスト結果を得ることが出来るように
なっているのだけど、"--drb"がデフォルトで付与されるようにするとかいう以前に
そもそもテストを走らせるっていう行為がだるいので、勝手に走ってくれるようにする。

まずWatchrの設定ファイルを記述する。

$ vi .watchr
def run_spec(file)
  unless File.exist?(file)
    puts "#{file} does not exist"
    return
  end

  puts "Running #{file}"
  system "bundle exec rspec #{file}"
  puts
end

watch("spec/.*/*_spec.rb") do |match|
  run_spec match[0]
end

watch("app/(.*/.*).rb") do |match|
  run_spec %{spec/#{match[1]}_spec.rb}
end

次にこの設定ファイルを読んで動くrakeタスクを記述する。

$ vi lib/tasks/watchr.rake
desc "Run watchr"
task :watchr do
  sh %{bundle exec watchr .watchr}
end

で、Sporkが動いている状態で、"rake watchr"するとファイルを変更したタイミングで
そのファイルに対応したspec(テスト)が実行されるようになる。

それぞれ別のコンソールで、
Sporkを起動、

$ spork
Using RSpec, Unknown
Loading Spork.prefork block...
Spork is ready and listening on 8989!

Watchrを起動、

$ rake watchr
rake watchr .watchr
/home/appname/vendor/bundle/ruby/1.9.1/gems/watchr-0.7/lib/watchr.rb:111: \
Use RbConfig instead of obsolete and deprecated Config.

ファイルを更新すると自動的にRspecが走り

$ vi app/models/user.rb
:w

Watchrを動かしているコンソールに結果が表示される。

Pending:
  User add some examples to (or delete) /home/appname/spec/models/user_spec.rb
    # No reason given
    # ./spec/models/user_spec.rb:4

Finished in 0.01407 seconds
1 example, 0 failures, 1 pending

Randomized with seed 61875

はず。

5th. 追加の設定

この状態だとモデルと"spec/support"配下にある自作のスクリプトが
Sporkの起動毎にしか読みにいってくれないので、常に再読込してくれるように設定する。

まず、モデル

$ vi config/environments/test.rb

# config.cache_classes = true
config.cache_classes = false

次に"spec/support"配下、Spork.prefork内にある次の部分を

$ vi spec/spec_helper.rb
Spork.prefork do
  ~~
  # 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}
  ~~
end

Spork.each_runの中に移動する。

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

  # 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}
end

参考ページ

How To Get Rails 3 and RSpec 2 Running Specs Fast (From Scratch)