patorashのブログ

方向性はまだない

CircleCIで前に落ちたテストを先に検証するWorkflowを組んだ

昨日、CircleCIで失敗したテストのファイル一覧を取得するという記事を書きました。

patorash.hatenablog.com

今度はそれを使って、CircleCIで前に落ちたテストを先に検証するワークフローを組みました。

こんな感じです。

f:id:patorash:20190208101246p:plain
CircleCIで再テストを先に行うワークフロー

何故そうしたのか?

以前に落ちたテストはまた落ちる可能性が高い、と感じています。全体テストを流すと結果が出るまで長時間かかりますが、以前に落ちたところだけ流すのは数分で済みます。そして、落ちたらそこでテストは終了するので、時間の節約ができる!と思ったわけです。

もし再テストを通過したら全体テストが行われます。そうなると、再テストの後に全体テストとなるので、その分時間はかかります。

CircleCIの設定を行なう

ワークフローを組む

まずは先にワークフローの部分を書いていきます。

version: 2.1

# jobの設定などは後述

workflows:
  build:
    jobs:
      - prepare_test
      - retry_failed_test:
          requires:
            - prepare_test
      - test:
          requires:
            - retry_failed_test

全体テストのジョブ(test)のrequiresに再テストのジョブ(retry_failed_test)を指定しているので、再テストが失敗したら全体テストは流れなくなります。

retry_failed_testを定義する

では、該当ジョブを定義します。

version: 2.1

# executors, commandsは省略

jobs:
  # prepare_test, testは省略

  retry_failed_test:
    executor: rspec
    parallelism: 4
    steps:
      - restore_code_cache
      - run: bundle --path vendor/bundle
      - run: bundle exec chromedriver-update
      - setup_database
      - run:
          name: Run failed tests if exists
          command: |
            FAILED_FILES=$(bin/rails runner script/circleci_failed_spec_files.rb --line)
            if [ -n "$FAILED_FILES" ]; then
              TEST_FILES=$(echo -e "$FAILED_FILES" | circleci tests split --split-by=filesize)
              if [ -n "$TEST_FILES" ]; then
                bin/rspec ${TEST_FILES}
              fi
            fi

      - store_test_results:
          path: tmp/test-results

      - store_artifacts:
          path: /tmp/test-results
          destination: test-results
該当箇所の説明

肝の箇所はここです。

- run:
    name: Run failed tests if exists
    command: |
      FAILED_FILES=$(bin/rails runner script/circleci_failed_spec_files.rb --line)
      if [ -n "$FAILED_FILES" ]; then
        TEST_FILES=$(echo -e "$FAILED_FILES" | circleci tests split --split-by=filesize)
        if [ -n "$TEST_FILES" ]; then
          bin/rspec ${TEST_FILES}
        fi
      fi
  1. 以前のテストで失敗したファイルがあれば、ファイル名を行毎に出力したものを変数FAILED_FILESに入れる
  2. $FAILED_FILESの中身がなければ何もせず終了。
  3. $FAILED_FILESの中身があれば、落ちたテスト一覧を並列テストするために内容をecho -eで出力して、その結果をpipeでcircleci tests split --split-by=filesizeに渡します(--split-byの指定はお好きに変更を)。そうすることで、コンテナ毎にテスト対象ファイル名が変数TEST_FIELSに配布されます。
  4. $TEST_FILESの中身がなければ、そのコンテナでは何もせず終了。
  5. $TEST_FILESの中身があれば、テストを実行。

感想

テストは全体では15分くらいかかるようになってしまいましたが、retry_failed_testのところで落ちたら5分で終わるようになったので、いいかなと思います。もし新規ブランチで初のテストの場合は落ちたテストがないのでretry_faile_testのジョブも1分くらいで終了しますし、問題ないくらいかなと思います。