Thanks Driven Life

日々是感謝

test-queue で worker 毎に設定値を変更したくなったらこうする

経緯

  • RSpec + Capybara を test-queue で動かすぞ!!
  • app サーバはローカルで rack で、とかじゃなくて別ホストに立ってるものとする
  • いろいろあって全 worker が同じ app サーバを見ると困る
  • Capybara.app_host を使えばアクセス先を変更できる
  • ↑ これを worker 毎に違う値設定できればいいんじゃないかな

同じ app サーバを見せたくないだけなら NginX など使って load balancing すればいいじゃないかみたいなのはとりあえず置いといてください。世界が厳しいのです。

解決方法

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.

つまり TestQueue::Runner::使用するテストフレームワーク を継承するクラスを作って、 それぞれメソッドがプロセス空間内?で呼び出されるからよしなにやってくれ、というものです。

解決例

今回は Capybara + RSpec で動く test-queue 環境を作りたいので、TestQueue::Runner::RSpec を使います。

$ emacs my_app_test_runner

#!/usr/bin/env ruby

require 'test_queue/iterator'
require 'test_queue/runner/rspec'
require 'rspec'

class MyAppTestRunner < TestQueue::Runner::RSpec
  #
  # num は worker ID (1,2,...)
  #
  def after_fork(num)
    Capybara.app_host = "http://app#{num}.example.com"
  end
end

MyAppTestRunner.new.execute

after_fork()fork された RSpec プロセス内で呼び出される ので、 このメソッド内で設定した環境変数グローバル変数はプロセスに閉じます。

続いてこのファイルを rspec-queue コマンドの代わりに呼び出します。 今回は worker 数を 5 としてみましょう。

# 標準の RSpec ランナーを実行する場合は
# bundle exec rspec-queue spec/

$ TEST_QUEUE_WORKERS=5 bundle exec ruby my_app_test_runner spec/

このように実行すると、各 worker はそれぞれ以下の URL に対しアクセスします。

  • worker[1] = http://app1.example.com
  • worker[2] = http://app2.example.com
  • worker[3] = http://app3.example.com
  • worker[4] = http://app4.example.com
  • worker[5] = http://app5.example.com

無事 worker が乱れ飛ぶようなテストが走らないようになりました。やったね!

おまけ(RSpec のレポートを worker 毎に出力したい)

test-queue を使うとテストケース自体は並列に実行されますが、その結果となるレポートの出力先までは複数に別れていません。 というのも fork する前に RSpec.configuration を設定しているから です。

なのでこの場合も after_fork() 内に書く作戦でいきましょう。

#!/usr/bin/env ruby

require 'test_queue/iterator'
require 'test_queue/runner/rspec'
require 'rspec'

class MyAppTestRunner < TestQueue::Runner::RSpec
  def after_fork(num)
    Capybara.app_host = "http://app#{num}.example.com"

    # 追加
    ::RSpec.configure do |config|
      config.add_formatter 'html', "report#{num}.html"
    end
  end
end

MyAppTestRunner.new.execute

これで worker 数5 で実行すると、最終的に report1.html から report5.html が生成されます。

ちなみに

parallel_tests の場合は

  1. プロセス作られる
  2. ENV['TEST_ENV_NUMBER'] って環境変数に worker ID がセット
  3. ここで RSpec.configuration が呼ばれる

みたいになるので、.rspec とか .rspec_parallel

-f html -o report<%= ENV['TEST_ENV_NUMBER'] %>.html

という技が使えます。

まとめ

test-queue 便利!