RailsアプリのテストをRSpecで書いていて、examplesの数が増えていくと、 RSpecの実行時間が長くなっていきます。
この実行時間を短縮する方法を調べていて、test-queueというライブラリにたどり着いたので、 使い方を纏めておきます。
test-queueは、テストを並列実行するテストランナーで、 MITライセンスのオープンソースとして公開されている。
種々のテストフレームワークでのテストケースを並列実行するツールで、
に対応している。
試した環境は、以下の通り。
Railsアプリにtest-queueを導入するには、Gemfileに
group :development, :test do
gem 'test-queue'
end
を追記し、bundle install
する。
この時点で、この時点で、test-queueが提供するRSpec用のランナーであるrspec-queue
が使えるようになる。
試しに、個人的に開発しているアプリの環境で実行してみた。
bundle exec rspec-queue spec
すると、
1) UsersController GET #show when an user is logged in, returns https status 200.
Failure/Error: @user = FactoryGirl.create(:user)
ActiveRecord::RecordInvalid:
Validation failed: Email has already been taken, Name has already been taken
# ./spec/controllers/users_controller_spec.rb:9:in `block (4 levels) in <top (required)>'
のようにModelのValidationでエラーが発生し、テストに失敗する。
(上記のエラーは、テストデータの投入時にModelのUniqueness Validatorにひっかかり、テストが失敗した。 もちろん、test-queueを使わない環境では、正常にパスするテストケース。)
これは、test-queueが生成する複数のワーカープロセスが、同一のデータベースにアクセスすることで発生する。 ワーカー単位で異なるデータベースを使用するようにする必要がある。
実際、test-queueのREADMEには、
Since test-queue uses fork(2) to spawn off workers, you must ensure each worker runs in an isolated environment. Use the after_fork hook with a custom runner to reset any global state.
とある。また、
But the underlying TestQueue::Runner::TestUnit, TestQueue::Runner::MiniTest and TestQueue::Runner::RSpec are built to be subclassed by your application. I recommend checking a new executable into your project using one of these superclasses.
ともある。つまり、
のようなので、やはり、自作のランナーを作って、複数のワーカーが独立して動くようにする必要がある。
test-queueのワーカー単位で異なるデータベースにアクセスするための独自のランナーを次のように実装してみた。
#!/usr/bin/env ruby | |
ENV['RAILS_ENV'] ||= 'test' | |
require File.expand_path('../../config/environment', __FILE__) | |
require 'test_queue' | |
require 'test_queue/runner/rspec' | |
class RSpecQueueRunner < TestQueue::Runner::RSpec | |
def after_fork(num) | |
ENV.update('TEST_ENV_NUMBER' => num.to_s) | |
ActiveRecord::Base.configurations['test']['database'] << num.to_s | |
ActiveRecord::Base.establish_connection(:test) | |
ActiveRecord::Tasks::DatabaseTasks.drop_current | |
ActiveRecord::Tasks::DatabaseTasks.create_current | |
ActiveRecord::Tasks::DatabaseTasks.load_schema_current | |
end | |
end | |
RSpecQueueRunner.new.execute |
これを、bin/my-rspec-queue
に配置し、chmod +x
する。
上記のbin/my-rspec-queue
を実行すると、次のような結果が得られた。
% bin/my-rspec-queue spec
Starting test-queue master (/tmp/test_queue_16384_46915722628380.sock)
==> Summary (2 workers in 11.6600s)
[ 1] 44 examples, 0 failures, 6 pending 13 suites in 11.6425s (pid 16420 exit 0 )
[ 2] 49 examples, 0 failures, 6 pending 12 suites in 11.6322s (pid 16423 exit 0 )
上述のエラーが解消され、2つのワーカーで並行してテストを実行できた模様。 ちなみに、ワーカー数はデフォルトでシステムのコア数になる模様。
通常のbundle exec rspec
とbin/my-rspec-queue
で実行時間を比較してみたところ、
env | time(sec) |
---|---|
rspec | 25.668 |
test-queue | 18.676 |
となった。この検証でのRSpecのexample数は、93(うち、12pendings含む)。
へっぽこマシン、かつ100弱のテストケースでも約30%ほど実行時間が短縮されているので、 より現実的な環境であれば、さらにtest-queueの能力が際立つと思われる。
以下のサイトを参考にさせて頂きました。
以上、test-queueを使ってRSpecの実行時間を短縮する方法について纏めてみました。
大量のテストケースを高速マシンで処理させてみたいものです。