drone.io(OSS版)で並列テストを実行してビルド時間を削減する


プロダクトが成長してテストコードの規模が大きくなってくるとテスト実行時間も延びてきます。成長は嬉しいけど、ビルド時間が延びるのは嬉しくないです。そこでテストを並列で実行できるようにdrone.ioを設定してみます。

なお、CircleCI2.0での並列テストの方法は同僚が試してくれました。以下の記事を参考にしてください。

マトリックスビルド

drone.ioにはマトリックスビルドという機能があります。これを使うことで複数のworker(Dockerコンテナ)を並列で実行させられます。設定は簡単です。

.drone.yml


pipeline:
  build:
    image: hoge/piyo
  commands:
    - RAILS_ENV=test bundle exec rspec ${TESTFOLDER}

matrix:
  TESTFOLDER:
    - spec/models
    - spec/requests

これだけで2つのworkerが立ち上がり、それぞれで spec/modelsspec/requests のテストを実行してくれます。やったね🎉 でも実際これは上手くいきません。なぜなら最も実行時間のかかるリクエストテストが1つのworkerに集中してしまっているからです。片方はすぐ終わるのに、結局全体の実行時間はあまり改善されません(CircleCIなら勝手にランダムにしてくれるんだけどな…)。

テストコードファイルを分散する

というわけで、テストコードのファイルを分散するようにします。雑に書いたスクリプト例です。

require 'fileutils'
require 'pathname'

class RandomDistSpec

  def initialize
    FileUtils.rm_rf("matrix_specs")
    @worker_num = 4
  end

  def copy_ruby_spec
    specs = Dir.glob("spec/**/*_spec.rb").shuffle
    copy specs
  end

  private
  def copy specs
    slice_num = (specs.size / @worker_num).ceil

    1.upto(@worker_num) do |i|
      FileUtils.mkdir_p("matrix_specs/#{i}") unless FileTest.exist?("matrix_specs/#{i}")

      files = specs.slice!(0, slice_num)
      files.each do |file|
        dir = Pathname(file).dirname.to_s
        FileUtils.mkdir_p "matrix_specs/#{i}/#{dir}"
        FileUtils.cp file, "matrix_specs/#{i}/#{file}"
      end
    end
  end
end

spec = RandomDistSpec.new
spec.copy_ruby_spec

こうすることで、 /matrix_specs/n ディレクトリにランダムにファイルが配置されます。合わせて .drone.yml も修正します。

matrix:
  TESTFOLDER:
    - matrix_specs/1
    - matrix_specs/2

これでテスト実行時間が無事削減できました。

CIは非常に便利ですが、CIでのビルド時間が長いと改善サイクルが上手く回せません。待ち時間が発生し、集中力を取り戻すのにコストがかかります。なのでCIは積極的に改善をしていくべきところだなぁと思いました。