LoginSignup
3
2

More than 3 years have passed since last update.

CircleCI で並列実行した RSpec のカバレッジを Coveralls に送る

Last updated at Posted at 2020-04-04

Coveralls に以下のドキュメントがあるのだけど、書いてある通りにやってもうまくいかなかったので、なんとかうまくいった方法を書いておきます。

やりたいこと

RSpec を並列で実行し、すべてのインスタンスの終了を待って、別のジョブで各インスタンスのカバレッジの結果をマージして Coveralls に送信したい。

  • ドキュメントには Coveralls.wear_merged! を呼んで最後に coveralls:push を実行しろとあるけれど、coveralls:push (Coveralls.push!) で使われている SimpleCov::ResultMerger.merged_result同じ環境で実行された 複数のテストのカバレッジ結果をマージするものであり、違う環境で実行された結果をマージできるわけではない。
  • Webhookを使う方法もあるけれど、テストを実行するジョブと結果を送信するジョブが違うので、結果を送信するときに payload[build_num] を指定することができない。

やり方

Gemfile
group :test do
  gem 'coveralls', '~> 0.8.23', require: false
end

RSpec

Coveralls.wear! は Coveralls にカバレッジを送信するけれど、Coveralls.wear_merged! は送信しない(SimpleCov を実行してるだけ)。

spec/rails_helper.rb
if ENV['COVERALLS_REPO_TOKEN']
  require 'coveralls'
  Coveralls.wear_merged!('rails')
end

# [...]

カバレッジ結果をマージ・送信する Rake Task をつくる

SimpleCov の README にある "Merging test runs under different execution environments" の通りにやればいいのだけど、Coveralls の gem が依存している SimpleCov のバージョンが古くて、SimpleCov.collate が存在しない。よって自分でつくる必要がある。

lib/tasks/coverage.rake
namespace :coverage do
  desc 'Collate all result sets and push to Coveralls'
  task push: %i[environment collate] do
    require 'coveralls'
    Coveralls.push!
  end

  desc 'Collate all result sets generated by the different test runners'
  task collate: :environment do
    require 'simplecov'

    # coverage/.resultset-*.json を coverage/.resultset.json にまとめる
    # see: https://github.com/colszowka/simplecov/blob/v0.18.5/lib/simplecov.rb#L80

    results = Dir['coverage/.resultset-*.json'].flat_map do |filename|
      (JSON.parse(File.read(filename)) || {}).map do |command_name, coverage|
        SimpleCov::Result.from_hash(command_name => coverage)
      end
    end

    SimpleCov::ResultMerger.merge_results(*results).tap do |result|
      SimpleCov::ResultMerger.store_result(result)
    end
  end
end

CircleCI

.circleci/config.yml
version: 2.1

executors:

  app:
    docker:
      - image: circleci/ruby:2.6.5-node-browsers
        environment:
          BUNDLE_PATH: vendor/bundle
          RAILS_ENV: test

jobs:

  test:
    executor: app
    parallelism: 2
    steps:
      - checkout

      # [...]

      - persist_to_workspace:
          root: .
          paths:
            - .

      - run:
          name: RSpec in parallel
          command: bin/rspec $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)

      # coverage/.resultset.json にカバレッジ結果が生成されているので、
      # 名前がぶつからないように別の名前を付けたファイルを persist_to_workspace する
      - run:
          name: Prepare to collate coverage results
          command: cp coverage/.resultset.json "coverage/.resultset-${CIRCLE_NODE_INDEX}.json"

      - persist_to_workspace:
          root: .
          paths:
            - coverage/.resultset-*.json

  report:
    executor: app
    steps:
      - attach_workspace:
          at: .

      # こんな感じになっているはず
      # ./coverage
      #   .resultset-0.json
      #   .resultset-1.json

      - run: bin/rake coverage:push

workflows:
  version: 2

  test-report:
    jobs:
      - test
      - report:
          requires:
            - test

できた

#!/bin/bash -eo pipefail
bin/rake coverage:push
Running via Spring preloader in process 81
[Coveralls] Submitting to https://coveralls.io/api/v1
[Coveralls] Job #132.1
[Coveralls] https://coveralls.io/jobs/XXXXXXXX
Coverage is at 100.0%.
Coverage report sent to Coveralls.
CircleCI received exit code 0
3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2