ESM アジャイル事業部 開発者ブログ

永和システムマネジメント アジャイル事業部の開発者ブログです。

Rails + minitest な環境で CircleCI の Rerun failed tests を有効化する手順

ふーが です。こんにちは!
明日はいよいよ大阪 Ruby 会議 03 ですね。

現地参加予定なので現地にいらっしゃる方はぜひお話ししましょう。弊社からも多くのメンバーが参加予定ですし、セッションの内容も興味をひかれるものばかりで今からとても楽しみです。

blog.agile.esm.co.jp

さて今回は、2 ヶ月ほど前にリリースされた CircliCI の新機能、Rerun failed tests を有効化してみたのでご紹介します。

Rerun failed tests とは

CI でのテストが失敗した際に、失敗したテストのみを再実行する機能です。失敗したテストのためにテスト全体を再実行する必要がなくなり効率がよくなります。また、これによって再実行時に消費するクレジットも少なく済むため、金銭的なコスト削減にもつながります。

詳しくは以下をご覧ください。

circleci.com

ここからは Rails + minitest な環境での有効化手順の一例として参考にしていただければと思います。

環境

  • Ruby 3.2.2
  • Ruby on Rails 6.1.6.1
  • minitest 5.16.2

有効化前後の比較

まずは Rerun failed tests の有効化前後で UI がどのように変わるかを見てみます。

失敗したワークフローの詳細画面に行くと、右上に "Rerun" ボタンがあります。こちらのドロップダウンメニューを開くと "Setup rerun failed tests" というメニューが出ているのがわかります。これは Rerun failed tests が有効化されていない状態です。

有効化が完了すると、メニュー名が以下のように "Rerun failed tests" に変化し、失敗したテストのみを実行する機能が利用できるようになります。

今回はこの状態、つまり Rerun failed tests を実行できる状態にするのがゴールです。

有効化手順

前提

今回 Rerun failed tests を有効化したプロジェクトでは System test 用のワークフローとそれ以外のテスト用のワークフローの 2 つにわかれていました。そのためそれぞれに対して Rerun failed tests を有効化していきます。

System test

まず System test 用のワークフローから見ていきます。有効化前のテスト実行部分は以下のように記述されていました。

# .circleci/config.yml
system_test:
  docker:
    (snip)

  steps:
    (snip)

    - run:
        name: Run system test
        command: bin/rails test $(circleci tests glob "test/system/**/*_test.rb" | circleci tests split --split-by=timings --time-default=10s)

    - store_test_results:
        path: test/reports

bin/rails test の引数として、CircleCI の glob コマンドで test/system 配下のテストファイル(= System test ファイル)を全て取得した上で、CircleCI のパラレル実行用コマンドに渡して実行している形です。こちらを Rerun failed tests が使えるようにするためには、circleci tests run コマンドを使用する形に変更する必要があります。
https://circleci.com/docs/rerun-failed-tests/#example-config-file-after

今回は以下のように変更しました。

# .circleci/config.yml
system_test:
  docker:
    (snip)

  steps:
    (snip)

    - run:
        name: Run system test
-         command: bin/rails test $(circleci tests glob "test/system/**/*_test.rb" | circleci tests split --split-by=timings --time-default=10s)
+         command: circleci tests glob "test/system/**/*_test.rb" | \
+           circleci tests run --command="xargs bin/rails test" | \
+           --verbose --split-by=timings --timings-type=file --time-default=10s

    - store_test_results:
        path: test/reports

変更した行の 1 行目では変更前と同様の手法で System test ファイルを全て取得しています。大事なのは 2 行目で、circleci tests run コマンドの --command オプションにテスト実行コマンドである bin/rails test を渡しています。このとき xargs コマンドを使用することで前段で取得した System test ファイルが渡されるので、結果として System test のみが実行される形になります。
3 行目は出力やパラレル実行に関するオプションで Rerun failed tests 特有のものではないため説明は割愛します。

System test 以外

続いて System test 以外のケースを見てみましょう。変更前の設定ファイルは以下のように記述されていました。

# .circleci/config.yml
system_test:
  docker:
    (snip)

  steps:
    (snip)

    - run:
      command: bin/rails test

とてもシンプルですね。こちらを Rerun failed tests を使用するためには以下のようにすればよさそうです。

# .circleci/config.yml
system_test:
  docker:
    (snip)

  steps:
    (snip)

    - run:
-       command: bin/rails test
+       command: circleci tests run --command="bin/rails test"

残念ながらこの設定ですと以下のような警告が出力されてテスト自体が実行されなくなってしまいます。

WARN[2023-09-05T01:22:03Z] No test names found in input source. If you were expecting test names, please check your input source.

この警告からわかるとおり、circleci tests run コマンドを使用する場合は必ずファイル名( またはテストケース名 )を渡してやる必要があります。これを踏まえて設定は以下のようになりました。

# .circleci/config.yml
system_test:
  docker:
    (snip)

  steps:
    (snip)

    - run:
-       command: bin/rails test
+       command: |
+         circleci tests glob "test/**/*_test.rb" | \
+           grep -v "test/system/*" | \
+           circleci tests run --command="xargs bin/rails test"

+   - store_test_results:
+       path: test/reports

いったん System test を含めたすべてのテストファイルを取得した上で、grep -v で System test ファイルを除外したものを circleci tests run に渡す形を取っています。
また、Rerun failed tests はアップロードされたテスト結果の XML ファイルをもとに再実行すべきテストを抽出しているようです。そのため、store_test_results の指定も必ず必要になります。

以上でそれぞれ Rerun failed tests が利用できるようになっているはずです。実際に利用できるかを確かめてみましょう。

動作確認

Rerun failed tests が実行できるかどうか、また、失敗したテストのみが実行されているかを確認していきます。確認のため、わざと落ちるテストケースをいくつか用意した上で CI を動かしてみました。

Rerun failed tests を有効化する設定を取り込んだ Pull Request を作成し CI を動かした結果、想定通りテストが失敗しました。

Failure:
TopTest#test_visiting_as_guest [/home/circleci/project/test/system/top_test.rb:13]:
(snip...)

40 runs, 158 assertions, 1 failures, 0 errors, 0 skips

このワークフローの詳細画面を開くと、無事にドロップダウンメニューに Rerun failed tests が表示されるようになりました🎉

こちらをクリックして実行してみます。失敗したテストのみが実行されるようになっているでしょうか。

Failure:
TopTest#test_visiting_as_guest [/home/circleci/project/test/system/top_test.rb:13]:
(snip...)

4 runs, 7 assertions, 1 failures, 0 errors, 0 skips

実行されるテスト数は減っているようですが、 1 run を期待しているところ 4 runs となっています。失敗しているテストが定義されているファイルでは 4 つのテストケースがあるのでファイルごと実行されているように見えます。
こちらに関しては、ドキュメントの FAQs に答えがありました。

Question: Will this functionality rerun individual tests?
Answer: No, it will rerun failed test classnames or test filenames (file) that had at least one individual test failure.
https://circleci.com/docs/rerun-failed-tests/#FAQs

残念ながら "失敗したテストケースのみ" の実行には対応していない、というのが現状のようです。 ただ、store_test_results の設定により保存される XML ファイルには失敗したテストケース名があるため、これをうまく扱えば実現は可能かもしれません。

とはいえ当初の目的通り Rerun failed tests を有効化し、失敗したテスト(が定義されているファイル)のみを実行する、という目的は達成できました。

Rerun failed tests を実行したワークフローを確認する方法

ワークフローを一覧したときに、どれが全実行したものでどれが Rerun failed tests で実行したものかが確認できると良いですよね。その点は一目でわかるようになっています。
ダッシュボードからプロジェクトを選択すると実行されたワークフローの一覧が表示されますが、Rerun failed tests で再実行したものについては FAILED TESTS RERUN というラベルがつきます。

おわりに

CI 実行の待ち時間は短ければ短いほどいいですよね。
そんな全人類の切なる願いにこの記事が少しでもお役に立てれば幸いです。


永和システムマネジメントでは全国からリモートワークで働く仲間を絶賛募集しています。

agile.esm.co.jp