僕は発展途上技術者

Circle CI で rspec を並列で動かす

Circle CI で rspec をたとえば2個とか3個のコンテナーで並列で動かす方法を調べた。

Circle CI の公式ドキュメントを始め、役立つ記事はいくつかあったのだが、それぞれ少しずつ情報が足りなかったりあるいは一部の手順はすでにわかっているものとして書かれていたりする。全部をひっくるめて総合しないとうまく動かすことができなかったので、自分用のメモのためにも完全な手順を書き残しておく。

タイミングデータを出力する

rspec を並列で動かすには、テスト対象の全ファイルをうまく分けて、2個あるいは3個のコンテナーそれぞれで実行する rspec に配分しなければならない。

この分け方には、単純にファイル名をアルファベット順に並べて均等に分けたり、ファイルサイズで分けるなどいくつかあるのだが、一番効率がいいのが、実行時間が均等になるように分ける --split-by=timings だ。

しかし、この方法を採用にするには、あらかじめ全テストの実行時間を記録したタイミングデータというものが必要で、いくつか設定を行わなければならない。

まず、Gemfile に

group :test do
  gem 'rspec_junit_formatter'
end

を追加し、bundle install する。

rspec を実行したときにタイミングデータを xml ファイルに出力するためには、この rspec_junit_formatter gem が必要だ。

.circle_ci/config.yml の設定

次に .circle_ci/config.yml の設定を以下のように変更する。 変更した部分だけを書き出している。

jobs:
  build:
    parallelism: 2

    steps:
      - run:
          name: Parallel RSpec
          command: |
            TESTFILES=$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings --timings-type=filename)
            bundle exec rspec --format progress --format RspecJunitFormatter -o tmp/test-results/rspec.xml -- $TESTFILES

      - store_test_results:
          path: tmp/test-results

parallelism オプションでは、いくつのコンテナーで並列処理を行ないたいかを設定している。2 と指定すれば、2台のコンテナーで並列処理をおこなう。

Parallel RSpec と名付けたステップでは、タイミングデータを元にして全対象ファイルを実行時間がなるべく均等になるように分割している。次に bundle exec rspec で rspec を実行しているのだが、各オプションを指定することでテストデータを tmp/test-results/rspec.xml に出力している。

タイミングデータを出力するだけでは駄目で、次に rspec を実行するときにこのタイミングデータを利用できるように保存しておく必要がある。その保存先を指定しているのが、store_test_results オプションだ。

設定を変更した直後、1回目に rspec を実行するときは、過去のタイミングデータはまだ保存されていない状態なので、timing data が見つからない旨のメッセージがログ画面に流れる。タイミングデータを利用してファイルを分割できるようになるのは第2回目からである。

以上の設定で、CircleCI 上で rspec を並列に動かすことができる。

rubocop も並列で動かす

rspec の前に rubocop を実行している場合、各コンテナーで同様に全ファイルをチェックするのは無駄に思える。

そこで、以下の設定を .circle_ci/config.yml に追加し、rubocop の対象ファイル app と spec 以下の全 *.rb ファイルをファイルサイズを元に分割して、実行するようにした。

jobs:
  build:
    steps:
      - run:
          name: Parallel rubocop
          command: |
            PROJECTFILES=$(circleci tests glob "app/**/*.rb" "spec/**/*.rb" | circleci tests split --split-by=filesize)
            bundle exec rubocop -c .rubocop.yml -- ${PROJECTFILES}

参考にした記事

プロフィール

株式会社まちクエスト代表、つくる社LLC代表。

Scratchで楽しく学ぶ アート&サイエンスRaspberry Piではじめる どきどきプログラミングを書きました。

オンラインコンテンツ: 大人のためのScratch

Amazonから図書館検索 Libron、iPhoneアプリ ひらがなゲーム かなぶん を作っています。

Email: webmaster at champierre dot com

Twitter @jishiha

最近のエントリー

アーカイブ