Github Actionsで並列テストをする

CircleCIを使っていて最近感動した機能に テストの並列実行 があるのだが、その仕組を使えばGithub Actionsでも並列テストできそうだよなぁと思ったのでやってみた。

CircleCIではどう実装しているのか

こんな感じ設定して ref

version: 2
jobs:
  test:
    docker:
      - image: circleci/<language>:<version TAG>
    parallelism: 4

こんな感じでテストの実行のところ書き換えて上げればテストが並列化される。 ref

`bundle exec rspec $(circleci tests glob "spec/**/*.rb" | circleci tests split --split-by=timings)`

しかもそれぞれ別のコンテナで動くのでお互いに影響をしあわない。すごくよい。

Github Acionsでどうするか

以下の順にやっていく

  1. 引数として全体の並列度とそれぞれのコンテナに連番を渡す
  2. テストを分割するスクリプトを作って、渡した値からそのコンテナが処理するテストの一覧を吐き出す
  3. 吐き出したテストの一覧をテストコマンドに渡す

今回はRailsでテストを実行しているけど、テストコマンドにテストファイルの一覧を渡す方式は、Rails固有の仕組みではないので少し改変すればだいたいなんでも使えると思う。

引数として全体の並列度とそれぞれのコンテナに連番を渡す

name: CI

on: [pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        parallelism: [3]
        id: [0,1,2]
    steps:
      - uses: actions/checkout@v1
      - name: Set up Ruby 2.6
        uses: actions/setup-ruby@v1
        with:
          ruby-version: 2.6.x
      - name: Build and test
        env:
          RAILS_ENV: test
        run: |
          sudo apt-get install -yqq libmysqlclient-dev
          gem install bundler
          bundle install --jobs 4 --retry 3
          bundle exec rails db:setup assets:precompile
          bundle exec rspec

テストを分割するスクリプトを作って、渡した値からそのコンテナが処理するテストの一覧を吐き出す

以下を {PROJECT_ROOT}/bin/test_splitterとして保存して実行権限を与えておく。(chmod +x bin/test_splitter) 今回はrubyで作ったけどどんな言語でも同じことはだいたいできるはず。

#!/usr/bin/env ruby
require "optparse"

parallelism = 3
index = 0

OptionParser.new do |opt|
  opt.on('-p', '--parallelism val', Integer) {|v| parallelism = v }
  opt.on('-i', '--index val', Integer) {|v| index = v }
  opt.parse!(ARGV)
end

spec_files = Dir.glob("spec/**/*_spec.rb").sort_by { |file| File.size(file) }
puts spec_files.select.with_index { |_, i| i % parallelism == index }.join(' ')

吐き出したテストの一覧をテストコマンドに渡す

引数として全体の並列度とそれぞれのコンテナに連番を渡す で書いた設定の最後にある bundle exec rspec の箇所を bin/test_splitter -p $ -i $ | xargs bundle exec rspec に書き換える

おしまい。

テストが通ったとき

3つに別れて実行されてテストがそれぞれ通っている。

テストが通った画像

テストが落ちたとき

こんな感じで赤くなる。

テストが落ちたときの画像

ちなみにfail_fastをtrueにするとこんな感じ

テストが落ちたときの画像