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でどうするか
以下の順にやっていく
- 引数として全体の並列度とそれぞれのコンテナに連番を渡す
- テストを分割するスクリプトを作って、渡した値からそのコンテナが処理するテストの一覧を吐き出す
- 吐き出したテストの一覧をテストコマンドに渡す
今回は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 ${{ matrix.parallelism }} -i ${{ matrix.id }} | xargs bundle exec rspec
に書き換える
おしまい。
テストが通ったとき
3つに別れて実行されてテストがそれぞれ通っている。
テストが落ちたとき
こんな感じで赤くなる。
ちなみにfail_fastをtrueにするとこんな感じ