うわっ…私のカバレッジ、低すぎ…? - GitHub ActionsでRSpecのカバレッジレポートを出力する
はじめに
本記事はプロもくチャット Advent Calendar 2023の3日目です。
RSpecのカバレッジ計測行うgemであるSimpleCovと、GitHub Actionでカバレッジレポートの出力を行うoctocovを組み合わせて、PR作成時にテストを実行してカバレッジを計測し、PRコメントにカバレッジレポートを出力します。
What is カバレッジ?
テストの網羅性を評価する指標で、コードのうちテストで実行される割合を示すものです。
この数値が100%に近づくほど、より多くのコードがテストによって検証されていると言えます。
カバレッジの種類として、line coverage, branch coverage, condition coverageなどがあります。
line coverage
コードの各行がテストで実行されたかどうかを測定します。
例えば、以下のようなメソッドをテストする場合に、処理Aを通るテストケースが1つあればカバレッジが100%となります。
def foo
if condition_a
# 処理A
end
end
line coverageは「コードの各行が実行されたか」、つまり「分岐の中を通ったか」を示すものであり、分岐条件そのものについては評価されません。
branch coverage
コード内の条件分岐(if文, ループ, switch文など)をどれだけ網羅できているかを測定します。
上記のfoo
メソッドで言うと、condition_a
がtrue, falseの2通りのテストケースがあればカバレッジ100%となります。
また、以下のbar
メソッドの場合は、condition_b && condition_c
, condition_d && condition_e
の組み合わせが
[true, true]
[true, false]
[false, true]
[false, false]
の4通りのテストケースがあればカバレッジ100%となります(condition_b
とcondition_c
、condition_d
とcondition_e
は個々に考える必要はありません)。
def bar
if condition_b && condition_c
# 処理B
end
if condition_d && condition_e
# 処理C
end
end
なお、branch coverageではあくまで「分岐を網羅しているか」が関心事であり、line coverageのように「各行が実行されたか」には関与しません。
後述のSimpleCovでのbranch coverage計測においては、分岐のないメソッドの場合、実行されなくてもカバレッジ100%と判定されます。
condition coverage
これに対して、条件分岐内の個々の条件にも着目して網羅率を計測するのがcondition coverageです。
上記のbar
メソッドにおいて、branch coverageではcondition_b && condition_c
を1つの条件とみなしていましたが、condition coverageではcondition_b
とcondition_c
を別個の条件とみなします。
つまり、condition_b
〜condition_e
のそれぞれについてtrue/falseを検証するため、
Codecovではあかんのか?
カバレッジ計測ツールとしてはCodecovも有名です。
カバレッジ計測自体はもちろんのこと、
- PRごとのカバレッジ差分を確認する
- テストされた/されていない箇所を行単位で表示する
など、様々な分析が可能になります。
しかし、Codecovの料金体系は以下のようになっており、
Open Source | Developer | Pro | Enterprise | |
---|---|---|---|---|
料金 | 無料 | 無料 | 有料 | 有料 |
ユーザー数 | 無制限 | 1ユーザー | カスタム | カスタム |
Publicリポジトリでの使用 | 無制限 | 無制限 | 無制限 | 無制限 |
Privateリポジトリでの使用 | 不可 | 無制限 | 無制限 | 無制限 |
料金 | 無料 | 無料 | 有料 | 有料 |
https://about.codecov.io/pricing/
- Open SourceプランとDeveloperプラン以外は有料
- Open Sourceプランだとユーザー数無制限だが、Privateリポジトリでは使えない
- DeveloperプランだとPrivateリポジトリでも無制限だが、ユーザーは1人しか使えない
という特徴があります。
なので、複数人かつPrivateリポジトリでの開発の場合は有料プランを使うしかなく、導入ハードルが上がってしまいます。
そこで今回は、Codecovを使うことなく
- PRごとのカバレッジ差分を確認する
こちらも含めたカバレッジレポートを無料で出力する方法を紹介していきます。
(ただしGitHub Actions自体にかかるコストは考えないものとする)
というわけでやっていきます。
なお、本記事ではRSpec(Rails) + SimpleCov + octocovで構築していますが、octocov自体はSimpleCov以外にも様々なフォーマットに対応しているので、他の言語でも使用できます(公式ドキュメントではGoを使って説明しています)。
手順
全体の流れ
以下のような流れで設定していきます。
- SimpleCovを使ってローカルでカバレッジ計測できるようにする
- octocovをGitHub Actionsのワークフローに組み込んで、PRコメントでカバレッジレポートを出力するようにする
- PRマージ時に、デフォルトブランチでカバレッジ計測を行うようにする
- 2のカバレッジレポートで、3の計測結果との差分も出せるようにする
登場人物はざっくりこんな感じです。
├── .github
│ └── workflows
│ ├── ci.yml # PR作成時に実行するCIのワークフロー
│ └── rspec_on_pr_merge.yml # PRマージ時に実行するCIのワークフロー
│
├── spec
│ └── spec_helper.rb # RSpecの設定ファイル(ここにカバレッジ計測の設定を記述する)
│
│── .octocov.yml # PR作成時に実行するカバレッジレポート出力の設定ファイル
└── .octocov.base.yml # PRマージ時に実行するカバレッジレポート出力の設定ファイル
カバレッジ計測の設定をする
まずはSimpleCovを使ったカバレッジ計測の設定を行います。
-
Gemfile
にSimpleCovを追加してbundle install
を実行するGemfilegem 'simplecov', require: false, group: :test
-
spec/spec_helper.rb
でSimpleCovを読み込むspec/spec_helper.rbrequire 'simplecov' SimpleCov.start
-
必要に応じて設定を追加する
spec/spec_helper.rbrequire 'simplecov' SimpleCov.start do enable_coverage :branch add_filter '/spec/' add_group 'Services', 'app/services' end
-
enable_coverage
: 指定したカバレッジを有効にする(デフォルトではline coverageのみ有効になっている)(後述) -
add_filter
: 指定したファイルが計測対象から除外される(例の場合、specディレクトリ以下のファイルが除外) -
add_group
: 指定した内容をグループ化して出力する(後述)
-
この状態でRSpecを実行すると、coverage
ディレクトリにカバレッジ計測結果が出力されます。
coverage/index.html
を開くと、画像のようなページが表示されます。
上述のenable_coverage :branch
の設定により、branch coverageの計測結果が表示されています(画像赤枠部分)。
また、add_group: 'Services', 'app/services'
の設定により、app/services
のファイルの計測結果がグループ化され、タブが追加されています(画像青枠部分)。
なお、Controllers, Channels, Models, Mailers, Helpers, Jobs, Librariesはデフォルトでグループ化されています。
ファイル毎の計測結果もあり、どの行が実行された/されてないかも確認することができます。
octocovの設定をする
次に、SimpleCovでの計測結果から、octocovを使ってPRコメントにカバレッジレポートを出力します。
前提として、GitHub ActionsでCIを構築済で、RSpecの実行までできるようになっているものとします。
-
GitHub Actionsのワークフローに、カバレッジレポートを出力するoctocov-actionを追加
.github/workflows/ci.ymlname: Rspec on: pull_request: branches: - main jobs: rspec: runs-on: ubuntu-latest timeout-minutes: 15 steps: # Ruby 3.2.2, Node 18の環境をセットアップ - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: 3.2.2 - uses: actions/setup-node@v4 with: node-version: 18 # RSpecを実行するための環境をセットアップ - run: gem install bundler:2.4.22 - run: ./bin/bundle install - run: ./bin/yarn install - run: ./bin/rails db:create - run: ./bin/rails db:migrate - name: Exec rspec run: ./bin/rspec + # octocovによりカバレッジレポートをPRコメントで表示 + - name: Coverage Report by octocov (in this pull request) + uses: k1LoW/octocov-action@v0
-
設定ファイル
.octocov.yml
を作成する.octocov.ymlcoverage: # カバレッジ計測の設定 paths: - coverage/.resultset.json # ここで指定した計測結果ファイルを読み込んで分析を行う acceptable: 60% # カバレッジがこの数値を下回るとCIがfailする testExecutionTime: # テスト実行時間 steps: - Exec rspec # この名前に対応するGitHub Actionsのstepの実行時間を計測する acceptable: 3min # 実行時間がこの時間を上回るとCIがfailする codeToTestRatio: # Code to Test Ratio(実装コードの行数を1とした場合のテストコード行数の比率)の設定 code: - "app/**/*.rb" test: - "spec/**/*_spec.rb" acceptable: 1:1.2 # この数値を下回るとワークフローがfailする comment: # レポート内容をコメント投稿設定 if: is_pull_request # PRの場合のみコメントを投稿する hideFooterLink: false # フッターのoctocovのリンクを非表示にしない report: # レポートの出力設定 datastores: - artifact://${GITHUB_REPOSITORY} # レポートの出力先(GitHub ActionsのArtifactsに保存する)
この設定でCIを回すとこんな感じのPRコメントが投稿されます。
カバレッジを含むテストの情報が表示できましたが、まだこのPR単体での結果のみです。
続いて、このカバレッジレポートを、デフォルトブランチでのカバレッジレポートと比較するようにします。
大まかな流れは、「PRマージ時にRSpecを実行してカバレッジレポートを出力する → 別のPR作成時にそのレポートと比較する」という感じです。
PRマージ時に、デフォルトブランチでRSpecを実行してカバレッジレポートを出力する
-
PRマージ時のカバレッジレポート用のoctocov設定ファイルを作成する
.octocov.base.ymlcoverage: paths: - coverage/.resultset.json testExecutionTime: steps: - Exec rspec codeToTestRatio: code: - "app/**/*.rb" test: - "spec/**/*_spec.rb" report: datastores: - artifact://${GITHUB_REPOSITORY}/base # .octocov.ymlとは違う保存先を指定する
-
PRマージ時にRSpecを実行するワークフローを作成する
.github/workflows/rspec_on_pr_merge.ymlname: RSpec on PR merge on: push: branches: - main jobs: rspec: runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: ruby-version: 3.2.2 - uses: actions/setup-node@v4 with: node-version: 18 - run: gem install bundler:2.4.22 - run: ./bin/bundle install - run: ./bin/yarn install - run: ./bin/rails db:create - run: ./bin/rails db:migrate - run: mv .octocov.base.yml .octocov.yml # 重要 - name: Exec rspec run: ./bin/rspec
このワークフローでは、run: mv .octocov.base.yml .octocov.yml
の行が肝になります。
基本的に.octocov.yml
がoctocovの設定ファイルとして認識されるため、.octocov.base.yml
をリネームして上書きする必要があります。
他のステップは.github/workflows/ci.yml
と同じなので、composite actionを使って共通化するとbetterでしょう。
octocovでデフォルトブランチとの比較を行う
前項で作成したデフォルトブランチのカバレッジレポートとの比較を行うため、.octocov.yml
の設定を追加します。
coverage: # カバレッジ計測の設定
paths:
- coverage/.resultset.json # ここで指定した計測結果ファイルを読み込んで分析を行う
- acceptable: 60% # カバレッジがこの数値を下回るとCIがfailする
+ acceptable: current >= 60% && diff >= 0% # カバレッジが60%以上かつ比較対象との差分が+0%以上でなければCIがfailする
testExecutionTime: # テスト実行時間
steps:
- Exec rspec # この名前に対応するGitHub Actionsのstepの実行時間を計測する
- acceptable: 3min # 実行時間がこの時間を上回るとCIがfailする
+ acceptable: current <= 3min && diff <= 1min # 実行時間が3min以内かつ比較対象との差分が1min以内でなければCIがfailする
codeToTestRatio: # Code to Test Ratio(実装コードの行数を1とした場合のテストコード行数の比率)の設定
code:
- "app/**/*.rb"
test:
- "spec/**/*_spec.rb"
- acceptable: 1:1.2 # この数値を下回るとワークフローがfailする
+ acceptable: current >= 1.2 && diff >= 0 # Code to Test Ratioが1.2以上かつ比較対象との差分が0以上でなければCIがfailする
comment: # レポート内容をコメント投稿設定
if: is_pull_request # PRの場合のみコメントを投稿する
hideFooterLink: false # フッターのoctocovのリンクを非表示にしない
report: # レポートの出力設定
datastores:
- artifact://${GITHUB_REPOSITORY} # レポートの出力先(GitHub ActionsのArtifactsに保存する)
+diff:
+ datastores:
+ - artifact://${GITHUB_REPOSITORY}/base # .octocov.base.ymlのreport.datastoresで設定した保存先を指定する
coverage
, testExecutionTime
, codeToTestRatio
の各項目では、今回の実行結果の数値をcurrent
、比較対象の数値をpref
、その差分をdiff
という値で用いることができます
この状態でCIを回すと、無事以下のように差分を含んだカバレッジレポートが出力されるようになりました🎉
(画像ではデフォルトブランチをmain
ではなくdevelop
としています)
まとめ
以上の方法で、Codecovを使用せずとも差分を含むカバレッジレポートを出力することができました。
PRコメントとして結果を出すようにすることで、「うわ…私(たち)のカバレッジ、低すぎ…?」という気付きを共有しやすくもなりますし、レビューで「テスト書きましょうね〜」とも言いやすくなります。
もちろんカバレッジのみがテスト指標の全てではありませんが、こういう見える化がテストを書く意識付けにも繋がれば素晴らしいですね!
最後までご覧いただきありがとうございました。
参考
Discussion