CircleCIを使ってテストを実行する場合、テストはcircleci tests split
コマンドで分割した上で複数コンテナを使うことでテストの実行時間を短縮することができます(参考リンク)。ただ、コンテナ数を増やすのも限界がありますし*1、少し前までtiming-based splittingがCircleCI Workflowでは有効にならないという問題もあり、コンテナごとのテストケース数や実行時間に偏りがあって、結局一番遅いコンテナに引きづられてスループットが思ったより上がらないという問題がありました。*2
そこで、コンテナ上でさらにテスト(RSpec)を並列実行することで、コンテナごとの実行時間を短縮し、それによって全体のスループットを上げるというのを試みてみました。
Gem探し
RSpecを並列実行するGemは何個かあって、rrrspec, parallel_tests, rspec-parallelなどがありますが、一長一短があり、どれもしっくり来ない感じでした。悩みポイントとしては、こちらのqiitaの記事で書かれていることがかなり近いです。
こちらの記事のyuku_tさんがが作られたparallel-rspecも試してみたのですが、まだProduction Readyでないようで通常のRSpecの挙動と違うところがあり、自分のケースでは使えませんでした。
それで色々他を探した結果 parallel_split_test というGemに行き着きました。こちらは最小限のハックでRSpecを拡張していて、なおかつファイルベースではなくspecベースでテストを分散できるという、まさに自分の求めるものでした。*3
使ってみる on CircleCI
使い方は至ってシンプルで、 rspec
コマンドを parallel_split_test
に変えるだけです、rspecで使っているオプションは全部使える(はず)です。
CircleCI的な実行の仕方としてはこんな感じになると思います。デフォルトのmediumコンテナは2CPUなので並列実行数(PARALLEL_SPLIT_TEST_PROCESSES
)は2を指定しています。
export PARALLEL_SPLIT_TEST_PROCESSES=2 bundle exec parallel_split_test --profile 10 \ --format RspecJunitFormatter \ --out /tmp/test-results/rspec.xml \ --no-merge \ --format progress \ $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
--no-merge
オプション
このオプションはparallel_split_test独自のオプションです。これを付けない場合、 アウトプットで指定したファイル*4に各プロセスの実行結果がすべてマージされます。 RspecJunitFormatter
の場合各プロセスの実行結果それぞれが個別のxmlファイルになって、マージされるとxmlのフォーマットではなくなってしまうので、--no-merge
オプションをつけることで別々のファイルに書き出します。ファイル名は、もともと指定したものにプロセス番号が追加されます*5。CircleCIは複数の結果ファイルをよしなにマージして集計してくれるので、この方法で全部のテスト結果を集計できます。
パッチ当てた
また、使ってる中で何個かはまったポイントはPRだして修正したりして、自分の使っているケースでは今はちゃんと動いています。 https://github.com/grosser/parallel_split_test/pull/13 https://github.com/grosser/parallel_split_test/pull/14 https://github.com/grosser/parallel_split_test/pull/15
よかったら、使ってみて下さい!