概要
テストの実行を高速化するSporkと、自動化するGuardを組み合わせて、快適なテスト駆動開発(TDD)環境を作ります。
構成
Ruby |
1.9.2p180 (2011-02-18 revision 30909) [x86_64-darwin10.7.0] |
Rails |
3.1.0 |
RSpec |
2.6.0 |
Spork |
0.8.5 |
Guard |
0.7.0 |
guard-spork |
0.2.1 |
guard-rspec |
0.4.5 |
gemパッケージのインストール
以下のgemパッケージをインストールします。
Gemfileはこんな感じになっています。
group :development, :test do
gem 'rails3-generators'
gem 'sqlite3-ruby', :require => 'sqlite3'
gem 'ruby-debug19', :require => 'ruby-debug'
gem 'rspec'
gem 'rspec-rails', '>= 2.0.0'
gem 'spork', '>= 0.8.5'
gem 'guard'
gem 'guard-spork'
gem 'guard-rspec'
gem 'rb-fsevent'
gem 'growl'
gem 'factory_girl'
gem 'annotate', :git => 'git://github.com/ctran/annotate_models.git'
end
gemパッケージをインストールします。
$ bundle install
Sporkの設定
次のコマンドを実行すると、spec_helper.rbの先頭にSpork.preforkとSpork.each_runというメソッド呼び出しが追加されます。
$ bundle exec spork --bootstrap
Using RSpec
Bootstrapping /Users/tetsuyai/dev/projects/myapp/spec/spec_helper.rb.
Done. Edit /Users/tetsuyai/dev/projects/myapp/spec/spec_helper.rb now with your favorite text editor and follow the instructions.
Spork.preforkに渡したブロックはサーバー起動時に実行され、Spork.each_runに渡したブロックはテストを実行するたびに実行されます。
FILE: $RAILS_ROOT/spec/spec_helper.rb
@@ -1,3 +1,32 @@
+require 'rubygems'
+require 'spork'
+
+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.
+
+end
+
+Spork.each_run do
+ # This code will be run each time you run your specs.
+
+end
+
+# --- Instructions ---
+# - Sort through your spec_helper file. Place as much environment loading
+# code that you don't normally modify during development in the
+# Spork.prefork block.
+# - Place the rest under Spork.each_run block
+# - Any code that is left outside of the blocks will be ran during preforking
+# and during each_run!
+# - These instructions should self-destruct in 10 seconds. If they don't,
+# feel free to delete them.
+#
+
+
+
+
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
spec_helper.rbを開いて、もともとspec_helper.rbに定義されていた処理をすべて、Spork.preforkのブロックの中に移動します。
+require 'rubygems'
+require 'spork'
+
+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'
# 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
config.mock_with :rspec
# 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
end
+end
+
+Spork.each_run do
+ # This code will be run each time you run your specs.
+
+end
+
+# --- Instructions ---
+# - Sort through your spec_helper file. Place as much environment loading
+# code that you don't normally modify during development in the
+# Spork.prefork block.
+# - Place the rest under Spork.each_run block
+# - Any code that is left outside of the blocks will be ran during preforking
+# and during each_run!
+# - These instructions should self-destruct in 10 seconds. If they don't,
+# feel free to delete them.
+#
Sporkを使ったテスト
別のコンソールを開いてSporkを起動します。
$ bundle exec spork
Using RSpec
Loading Spork.prefork block...
Spork is ready and listening on 8989!
テストを実行します。
$ bundle exec rspec --drb spec/models/working_record_spec.rb
....
Finished in 0.04636 seconds
4 examples, 0 failures
Sporkを使った場合と使わなかった場合を比較してみました。
使った場合
$ time bundle exec rspec --drb spec/models/working_record_spec.rb
....
Finished in 0.03497 seconds
4 examples, 0 failures
real 0m1.514s
user 0m1.018s
sys 0m0.220s
使わなかった場合
$ time bundle exec rspec spec/models/working_record_spec.rb
....
Finished in 0.01397 seconds
4 examples, 0 failures
real 0m6.470s
user 0m5.173s
sys 0m0.980s
デフォルトで--drbオプションが指定されるように、.rspecに記述を追加します。
FILE: $RAILS_ROOT/.rspec
@@ -1 +1,2 @@
--colour
+--drb
更新されたクラスのリロード
Sporkはあらかじめテスト対象となるコードをロードしておくことで、テストを高速に実行しています。そのため、デフォルトではアプリケーションのコードを更新してもSporkには反映されません。しかし、そのたびにSporkを再起動していてはテストのリズムが悪くなるため、コードが更新されたらクラスをリロードするよう修正します。
FILE: $RAILS_ROOT/spec/spec_helper.rb
@@ -33,6 +33,11 @@ Spork.prefork do
# instead of true.
config.use_transactional_fixtures = true
end
+
+ if Spork.using_spork?
+ ActiveSupport::Dependencies.clear
+ ActiveRecord::Base.instantiate_observers
+ end
end
Spork.each_run do
FILE: $RAILS_ROOT/config/application.rb
@@ -50,5 +50,13 @@ module Myapp
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
+
+ # Part of a Spork hack. See http://bit.ly/arY19y
+ if Rails.env.test? && defined?(Spork) && Spork.using_spork?
+ initializer :after => :initialize_dependency_mechanism do
+ # Work around initializer in railties/lib/rails/application/bootstrap.rb
+ ActiveSupport::Dependencies.mechanism = :load
+ end
+ end
end
end
SporkとGuardの連携
次のコマンドを実行すると、Guardfileが作成されます。
$ bundle exec guard init spork
Writing new Guardfile to /Users/tetsuyai/dev/projects/myapp/Guardfile
spork guard added to Guardfile, feel free to edit it
FILE: $RAILS_ROOT/Guardfile
# A sample Guardfile
# More info at https://github.com/guard/guard#readme
guard 'spork', :cucumber_env => { 'RAILS_ENV' => 'test' }, :rspec_env => { 'RAILS_ENV' => 'test' } do
watch('config/application.rb')
watch('config/environment.rb')
watch(%r{^config/environments/.+\.rb$})
watch(%r{^config/initializers/.+\.rb$})
watch('spec/spec_helper.rb')
end
次のコマンドを実行すると、Sporkが起動します。
$ bundle exec guard start
Guard is now watching at '/Users/tetsuyai/dev/projects/daylife'
Starting Spork for RSpec
Using RSpec
Loading Spork.prefork block...
Spork is ready and listening on 8989!
Spork server for RSpec successfully started
次のコマンドを実行すると、Guardfileに以下のような記述が追加されます。
$ bundle exec guard init rspec
rspec guard added to Guardfile, feel free to edit it
FILE: $RAILS_ROOT/Guardfile
@@ -8,3 +8,22 @@ guard 'spork', :cucumber_env => { 'RAILS_ENV' => 'test' }, :rspec_env => { 'RAIL
watch(%r{^config/initializers/.+\.rb$})
watch('spec/spec_helper.rb')
end
+
+guard 'rspec', :version => 2 do
+ watch(%r{^spec/.+_spec\.rb$})
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
+ watch('spec/spec_helper.rb') { "spec" }
+
+ # Rails example
+ watch(%r{^spec/.+_spec\.rb$})
+ watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
+ watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.r
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
+ watch('spec/spec_helper.rb') { "spec" }
+ watch('config/routes.rb') { "spec/routing" }
+ watch('app/controllers/application_controller.rb') { "spec/controllers" }
+ # Capybara request specs
+ watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
+end
+
次のコマンドを実行すると、最初にすべてのスペックがテストされ、自動テストが始まります。
$ bundle exec guard start
Guard is now watching at '/Users/tetsuyai/dev/projects/myapp'
Starting Spork for RSpec
Using RSpec
Loading Spork.prefork block...
Spork is ready and listening on 8989!
Spork server for RSpec successfully started
Guard::RSpec is running, with RSpec 2!
Running all specs
FF.FFFF..FFFFFFF*.............F..
これ以降、ファイルを保存すると自動テストが実行されます。