🎯

GitHub Actionsで前回失敗したRSpecのテストケースを先に実行する

2023/03/12に公開

RSpecをGitHub Actionsで実行するWorkflowを作成しました。
同じPR内で前回失敗したテストケースがあれば、先にそれらを実行します。
これによりに失敗した箇所の修正がされたかをすばやく確認できます。

目的

前回テストに失敗した箇所が修正されたかを早く知るというのが目的です。
特に全体のテスト件数が多いとそれを知るまで時間がかることがあります。
修正が適切でない場合、同じ箇所のテストが再度失敗することがあるため、他のテストを実行するリソースや待ち時間の無駄を減らす狙いがあります。

実装

Rspecの設定

RSpecの実行結果を保存するファイルを設定します。
後でrspecの--only-failuresオプションを使って、前回失敗したテストのみ実行するため必要となります。
Gitで管理すると後述のWorkflowが意図した動きにならないためtmpディレクトリ配下にしています。

spec/spec_helper.rb
config.example_status_persistence_file_path = 'tmp/spec_examples.txt'

Workflowの作成

GitHub ActionsのWorkflowを作成します。以下がその例です

.github/workflows/rspec.yml
name: RSpec

on: pull_request

jobs:
  rspec:
    runs-on: ubuntu-latest

    env:
      RAILS_ENV: test

    steps:
    - uses: actions/checkout@v3

    - name: Install
      run: |
        sudo apt-get update && sudo apt-get install -y curl build-essential git sqlite3 libsqlite3-dev

    - name: Set up Ruby
      uses: ruby/setup-ruby@v1
      with:
        ruby-version: 3.2.1
        bundler-cache: true

    - name: Restore test results
      id: 'restore_test_results'
      uses: actions/cache/restore@v3
      with:
        path: tmp/spec_examples.txt
        key: ${{ runner.os }}-rspec-examples-${{ github.event.pull_request.number }}-${{ hashFiles('tmp/spec_examples.txt') }}
        restore-keys: ${{ runner.os }}-rspec-examples-${{ github.event.pull_request.number }}-

    - name: Run Rspec only failures
      run: |
        if [[ -e tmp/spec_examples.txt && $(grep -c 'failed' tmp/spec_examples.txt) -gt 0 ]]; then
          grep -E "(example_id)|(---)|(failed)" tmp/spec_examples.txt
          bundle exec rspec --format progress --only-failures
        fi

    - name: Run RSpec
      run: |
        bundle exec rspec

    - name: Cache test results
      if: always()
      uses: actions/cache/save@v3
      with:
        path: tmp/spec_examples.txt
        key: ${{ runner.os }}-rspec-examples-${{ github.event.pull_request.number }}-${{ hashFiles('tmp/spec_examples.txt') }}

実装は以上です。

Workflowの解説

テスト結果のキャッシュ

最後のステップでRspecの実行結果をキャッシュしています。
RSpecのテストが失敗してもキャッシュするためにif: always()にしています。
また、PRごとにキャッシュするために${{ github.event.pull_request.number }}keyに含めています。

    - name: Cache test results
      if: always()
      uses: actions/cache/save@v3
      with:
        path: tmp/spec_examples.txt
        key: ${{ runner.os }}-rspec-examples-${{ github.event.pull_request.number }}-${{ hashFiles('tmp/spec_examples.txt') }}

ちなみに、tmp/spec_examples.txtの内容は以下のようになります。

tmp/spec_examples.txt
example_id                            | status | run_time        |
------------------------------------- | ------ | --------------- |
./spec/models/post_spec.rb[1:1:1:1:1] | passed | 0.22244 seconds |
./spec/models/post_spec.rb[1:1:1:2:1] | failed | 0.0217 seconds  |
./spec/requests/posts_spec.rb[1:1:1]  | passed | 0.22766 seconds |

前回失敗したテストケースの取得と実行

このステップで前回のテスト結果を取得しています。
keyは必須なので指定していますが、tmp/spec_examples.txtはGitで管理していないため実質的に意味はありません。
実際にはrestore-keysによりキャッシュが復元されます。

- name: Restore test results
  id: 'restore_test_results'
  uses: actions/cache/restore@v3
  with:
    path: tmp/spec_examples.txt
    key: ${{ runner.os }}-rspec-examples-${{ github.event.pull_request.number }}-${{ hashFiles('tmp/spec_examples.txt') }}
    restore-keys: ${{ runner.os }}-rspec-examples-${{ github.event.pull_request.number }}-

続くステップで、前回失敗したテストケースがある場合にのみrspecの--only-failuresオプションを指定してそれらを実行しています。

- name: Run Rspec only failures
  run: |
    if [[ -e tmp/spec_examples.txt && $(grep -c 'failed' tmp/spec_examples.txt) -gt 0 ]]; then
      grep -E "(example_id)|(---)|(failed)" tmp/spec_examples.txt
      bundle exec rspec --format progress --only-failures
    fi

ちなみに、grep -c 'failed' tmp/spec_examples.txtによるfailedの存在確認はなくても動きます。すべてpassedのとき無駄にファイルロードの時間がかかるのでこうしています。

解説は以上になります。

余談

最近、GitLabのHandbookを読んで作ってみようと思ったのがきっかけです。

以上

Discussion