coverage-analysis について
coverage-analysisは、テストカバレッジの評価と改善を支援するQAスキルです。行カバレッジの数値だけでなく、ブランチ・ミューテーションカバレッジ、リスク別のギャップ分析、CIでの段階的な基準設定を扱います。
主な機能
- 01ブランチ・ミューテーションカバレッジの指針
- 02リスク別カバレッジギャップ分析
- 03CIのしきい値とラチェット方式
- 04カバレッジツールの選定
- 05カバレッジの統合とレポート生成
活用シーン
- 01決済や認証ロジックのテスト漏れを特定
- 02CIでカバレッジ低下を防止
- 03テストが注入された不具合を検出するか確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ petrkindlmann/ qa-skills --skill coverage-analysisインストール後、Claude Code で/coverage-analysisを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/ci-gating.mdmutation-testing.mdtool-config.md詳細
クイックガイド
| 状況 | 参照先 |
|---|---|
| カバレッジプロバイダーを選んで設定する | カバレッジツール → references/tool-config.md |
| 次にどこへテストを書くか決める | ギャップ分析 |
| CI でカバレッジの低下を防ぐ | CI ゲートとしてのカバレッジ → ラチェット方式 |
| PR ごとのカバレッジをレビュー担当者に示す | CI ゲートとしてのカバレッジ → PR 差分 |
| テストでコードは実行されるが、アサーションがない | ミューテーションテスト |
| 除外対象や目標値を決める | 意味のあるカバレッジと見せかけのカバレッジ |
確認事項
まず .agents/qa-project-context.md を確認してください。存在する場合はその内容を使い、すでに回答されている項目は飛ばしてください。次に、以下を確認します。
- どのテストランナーとカバレッジツールが設定されていますか?
vitest.config.*(coverage ブロック)、jest.config.*(coverageProvider)、.nycrc、スクリプト内のc8、またはpyproject.tomlの[tool.coverage]を確認してください。インストールするパッケージはテストランナーによって決まります。Vitest では c8 ではなく@vitest/coverage-v8を使います(「カバレッジツール」を参照)。 - 現在のカバレッジレベルはどのくらいですか? 既存のカバレッジコマンドを実行し、行・分岐・関数の各カバレッジ率を記録してください。これがラチェットのベースラインになります。
- CI でカバレッジがゲートされていますか? GitHub Actions / GitLab CI に
--coverage、coverageThreshold、fail_under、または--cov-fail-underがあるか確認してください。ゲートがなければ、カバレッジは飾りにすぎません。 - 目標値は何ですか。また、誰が設定しましたか? 根拠のない目標(「VP が80%と言った」など)は、数値の操作につながります。目標は切りのよい数値ではなく、リスク許容度とコードベースの成熟度を反映する必要があります。
基本原則
1. カバレッジが測るのは広さであり、深さではありません。 行が実行されたからといって、正しくテストされたことにはなりません。expect(true).toBe(true) は関数を実行しますが、何もアサートしません。カバレッジで分かるのはコードが実行されたことだけであり、テストがバグを検出できるかどうかではありません。それを測るのがミューテーションテストです。
2. 行カバレッジよりも分岐カバレッジが重要です。 三項演算子 condition ? a : b は、片方の分岐しか実行されていなくても、行カバレッジでは1行全体がカバーされたと見なされます。行カバレッジだけでは分岐カバレッジは保証されません。行だけでなく、分岐をゲートに設定してください。
function discount(price: number, isPremium: boolean): number {
return isPremium ? price * 0.8 : price;
}
// Line coverage: 100% (the line executed). Branch coverage: 50% (only the true branch ran).
expect(discount(100, true)).toBe(80);
// Fix — assert BOTH paths so branch coverage reaches 100%:
expect(discount(100, true)).toBe(80);
expect(discount(100, false)).toBe(100);
3. ラチェット方式では、下げずに上げ続けます。 現在のカバレッジを最小しきい値として記録します。すべての PR はその値以上を満たす必要があります。あらかじめ不自然な目標値を設定しなくても、時間とともにカバレッジが向上します。
4. 数値ではなく、リスクに応じて不足箇所に注目します。 カバレッジ85%のプロジェクトが、75%のプロジェクトより自動的に「優れている」わけではありません。重要なのは、テストされていない範囲に決済、認証、データ整合性のロジックが含まれているかどうかです。リスクに応じて不足箇所を分析してください。
5. 新しいコードにはレガシーコードより高い基準を設けます。 プロジェクト全体が65%でも、PR の新しいコードには90%以上を求めます。これにより、レガシーコードの書き直しを強いることなく、カバレッジの低下を防げます。
カバレッジツール
まずテストランナーでプロバイダーを選び、次にビルド出力にどれだけ適切に対応するかを確認してください。
| ランナー/コンテキスト | インストール | プロバイダー |
|---|---|---|
| Vitest | @vitest/coverage-v8(デフォルト)または @vitest/coverage-istanbul | coverage.provider: 'v8' / 'istanbul' |
| Jest | 同梱(coverageProvider: 'v8' または 'babel') | V8 または Istanbul/babel |
Vitest 以外の Node(node:test、通常の mocha) | c8 CLI | c8 <command> による V8 |
| レガシーな Istanbul CLI | nyc | Istanbul によるインストルメンテーション |
| Python | pytest-cov(coverage.py をラップ) | coverage.py |
内部では、次の 2 つのエンジンが使われています。
- V8 カバレッジ — V8 エンジンに組み込まれているため、ソースコードをインストルメントしません。高速で、Babel による変換も不要です。Vitest では
@vitest/coverage-v8をインストールしてください(c8ではありません。これは Vitest 以外のランナー向けのスタンドアロン CLI です)。通常のnode:testや mocha のプロジェクトでは、c8が同じ V8 データを扱う CLI ラッパーです。新しい Node/Vitest プロジェクトでは、これがデフォルトです。 - Istanbul — ソースコードをインストルメントします。低速ですが、トランスパイラーやバンドラーを介したマッピングの信頼性が高くなります。V8 のマッピングが不正確な場合は、
@vitest/coverage-istanbulまたはnycに切り替えてください。V8 のマッピングが不正確な場合の症状: カバーされていないと報告された行が空行、閉じ括弧、デコレーターに該当する場合や、カバーされている関数全体が赤く表示される場合です。これはソースマップが行を誤って割り当てていることを示します(特定の TS バンドラーや SWC 設定でよく発生します)。この症状が見られたら、Istanbul プロバイダーに切り替えてください。
Node のベースライン: 現在のバージョンは
c811.x とnyc18.x です。c8 11 は引き続き Node >=12 をサポートしますが、nyc 18 には Node 20 || >= 22 が必要です。Node 18 を使い続ける必要がある場合は、nyc@^17に固定してください(c8 11 は Node 18 で問題なく動作します)。新しいプロジェクトでは、Node 20 以降に標準化してください。
プロバイダーの設定(Vitest の coverage ブロック、.nycrc.json、Jest の coverageThreshold、pyproject.toml / .coveragerc.toml)、インストールコマンド、実行コマンドの全容については、references/tool-config.md を参照してください。
テスト種別をまたいだカバレッジのマージ
ユニットテスト、インテグレーションテスト、E2E テストの実行では、それぞれ部分的なカバレッジが生成されます。インテグレーションテストでのみカバーされている行が未カバーとして報告されないよう、これらを結合してください。
- Vitest — 複数のプロジェクト/設定として実行して Vitest にマージさせるか、
coverage-final.jsonの出力をマージします。 - nyc —
nyc merge .nyc_output merged.json && nyc report -t mergedで、別々の実行による.jsonファイルを結合します。 - coverage.py — 各スイートを
coverage run -pで実行した後にcoverage combineを実行します。
最初にマージし、マージ後の合計値を基準に判定してください。各スイートのカバレッジを個別に判定しないでください。
カバレッジレポートの種類
| レポーター | 出力 | 用途 |
|---|---|---|
text | ターミナルの表 | ローカルでの簡易チェック |
html | インタラクティブな HTML | ファイルをクリックして確認する詳細なローカル分析 |
lcov | lcov.info ファイル | SonarQube、Codecov、Coveralls との連携 |
json-summary | coverage-summary.json | CI スクリプト、PR コメント、ダッシュボード指標 |
cobertura | cobertura-coverage.xml | GitLab CI のカバレッジ表示 |
ギャップ分析
カバレッジレポートには、実行されなかった行と分岐が示されます。すべてのギャップが同じではありません。リスクに応じて優先順位を付けてください。
手順 1:レポートを生成します。
npm run test:coverage
# Open coverage/index.html in a browser
手順 2:未カバー行数でファイルを並べ替えます。 coverage-summary.json を解析し、ファイルを (total - covered) の降順で並べ替えて、上位 20 件に絞ります。JSON サマリーを読み込み、ファイル/行カバレッジ率/分岐カバレッジ率/未カバー数を出力する小さなスクリプトを用意すると、繰り返し実行できます。
手順 3:ギャップをリスクに対応付けます。
| ギャップの場所 | リスクレベル | 対応 |
|---|---|---|
| 決済処理 | 重大 | 直ちにテストを作成 |
| 認証/権限 | 重大 | 直ちにテストを作成 |
| データ検証 | 高 | 次のスプリントに追加 |
| エラー処理のパス | 高 | 次のスプリントに追加 |
| ユーティリティ関数 | 中 | 変更時にカバー |
| UI のフォーマット | 低 | リグレッションが起きやすい場合を除き、対応不要 |
| 生成コード | なし | カバレッジ対象から除外 |
行だけでなく、並べ替えには分岐カバレッジも含めてください。行カバレッジが 100%/分岐カバレッジが 50%のファイルは、行のみの並べ替えでは「完了」と判定される未テストのパスが隠れています。
CI ゲートとしてのカバレッジ
しきい値の設定
グローバルしきい値をプロジェクトの最低基準として設定し、さらに決済や認証などの重要なコードには、ユーティリティより厳しいディレクトリ別しきい値を設定します。Vitest では thresholds の下に glob キーを使用します(例:"src/payments/**": { lines: 95, branches: 90 })。Jest では coverageThreshold の下にパスキーを使用します。どちらもパスごとの上書きに対応しています。
グローバル、ディレクトリ別、および Jest のファイル別しきい値の設定については、references/ci-gating.md を参照してください。
ラチェットパターン
カバレッジを決して低下させないでください。現在の水準を最低基準として記録し、カバレッジが改善したら基準を引き上げます。ラチェットスクリプトは coverage-summary.json を読み込み、各メトリクスをコミット済みの .coverage-ratchet.json と比較して、低下があればビルドを失敗させます。カバレッジが改善した場合は、基準値を更新します。
.coverage-ratchet.json をコミットします(例:{ "lines": 82, "branches": 78, ... })。CI ではテストの後にラチェットスクリプトを実行します。メインブランチへのマージ時にカバレッジが改善していたら、更新されたラチェットファイルを自動コミットします。
完全な coverage-ratchet.ts スクリプトについては、references/ci-gating.md を参照してください。
PR 差分カバレッジゲート
PR の新規コードには、プロジェクトのベースラインより高いしきい値(例:90%)を求めます。CI では git diff --name-only origin/main...HEAD を使用して変更ファイルを特定し、coverage-summary.json からそれらのカバレッジを確認します。変更ファイルのカバレッジがしきい値を下回った場合、パイプラインを失敗させます。これにより、レガシーコードを書き換えずにカバレッジの低下を防げます。
davelosert/vitest-coverage-report-action@v2(JSON サマリーを読み込みます)または、変更ファイルに絞り込むスクリプトと marocchino/sticky-pull-request-comment@v2 を使って、差分をレビュアーに表示します。完全な PR ワークフローについては、references/ci-gating.md を参照してください。
ホスティングサービスの代替案: Codecov、Coveralls、Trunk Coverage は、マージをブロックするゲートやインライン注釈に対応した、差分 PR カバレッジ機能を標準で提供しています。多くのチームは独自の差分スクリプトよりも、これらを選んでいます。すでにカバレッジホストを利用していない場合は、いずれかを選んでください。Codecov と GitHub の組み合わせでは、codecov/codecov-action@v5 が lcov.info を読み込み、PR の差分コメントを自動投稿します。
ミューテーションテスト
ミューテーションテストでは、コードの実行だけでなく、アサーションの品質を測定します。ソースコードを変異させ(> を >= に変える、行を削除するなど)、テストが失敗するかどうかを確認します。ミュータントが生き残る場合、テストで検出できない実際のバグがあることを意味します。Stryker JS v9.6+ と Vitest 4.1+ を使えば、PR で変更されたファイルを対象に実行できるほどコストを抑えられます。Python には mutmut 3.x が対応しています。
対象範囲
コードベース全体に対するミューテーションテストはコストが高いため、段階的に実行してください。Stryker の incremental: true(JSON キャッシュ)と、git の差分に範囲を絞った --mutate を組み合わせると、変更されたファイルのみを再度ミューテーションします。mutmut も同様にパス単位でミューテーションします。対象は次に限定してください。
- 純粋なビジネスロジック(バリデーター、計算機、変換器)
- クリティカルパス(決済、認証、データ整合性)
- 行カバレッジは高いものの、アサーションに懸念があるコード(ブランチカバレッジが 90% を超えている一方、アサーションのバリエーションが少ないコード)
UI のレンダリング、つなぎ込みのコード、生成コードは対象外にします。
Stryker の設定(stryker.config.json、incremental、変更されたファイルのみを対象に実行)と mutmut の実行方法については、references/mutation-testing.md を参照してください。
スコアの読み方
ミューテーションスコアが 80% ということは、注入されたバグの 80% を検出できたことを意味します。カバレッジの割合より低くなるのは普通です。多くのミュータントは、カバレッジレポートですでに指摘されている未テストの分岐に入り込みます。注目すべきなのは、カバレッジが高く、ミューテーションスコアが低い状態です。コードは実行されていますが、アサーションでは動作を十分に検証できていません。
有意義なカバレッジと見せかけのカバレッジ
100% のカバレッジが通常は不適切な理由
100% を達成するには、次のようなものも含め、すべての行のすべての分岐をテストする必要があります。
- 発生し得ない状態に対するエラー処理
- 網羅的な switch 文のデフォルトケース
- 直接呼び出されることのないフレームワークのライフサイクルメソッド
- 破損したデータに対する防御的チェック
100% を達成するために書かれたテストは、内容が単純で壊れやすく、実際のバグを検出できないことがよくあります。
逓減効果
| カバレッジの範囲 | 価値 | 労力 |
|---|---|---|
| 0% ~ 60% | 高 — 主なパス、明らかなリグレッション | 低 |
| 60% ~ 80% | 中 — エラーパス、エッジケース | 中 |
| 80% ~ 90% | 低め — まれな組み合わせ、防御的なコード | 高 |
| 90% ~ 100% | 最小 — 到達不能なコード、フレームワークの内部処理 | 非常に高い |
ほとんどのプロジェクトでは、75~85% が最適な範囲です。クリティカルパス(決済、認証)は、より高い値(90% 以上)を目指してください。全体のしきい値は最適な範囲に設定し、決済/認証についてはディレクトリごとのしきい値を 90% 以上に設定します。
カバレッジ対象にしないもの
これらは、価値を加えずに分母を増やすため、除外してください。除外リストで実際の不足をひそかに隠せないよう、各除外の根拠を CONTRIBUTING/カバレッジに関する注記に記録します。
// vitest.config.ts / jest.config.js — exclude patterns
exclude: [
"**/*.d.ts", // Type definitions
"**/index.ts", // Barrel exports (re-exports only)
"**/*.stories.{ts,tsx}", // Storybook stories
"**/generated/**", // Auto-generated code (GraphQL, Prisma)
"**/migrations/**", // Database migrations
"**/__mocks__/**", // Test mocks
"**/types/**", // Type-only modules
]
割合以外の品質指標
| 指標 | 測定内容 | 確認方法 |
|---|---|---|
| ミューテーションスコア | テストは実際のバグを検出できるか | Stryker / mutmut |
| ブランチカバレッジ | すべての条件分岐がテストされているか | ブランチレポートを有効にした V8/Istanbul |
| クリティカルパスのカバレッジ | 決済/認証/データのフローがすべて網羅されているか | ディレクトリごとのしきい値 |
| 欠陥流出率 | 本番環境のバグがテスト済みコードで発生しているか | インシデント後の分析 |
| カバレッジの増減 | カバレッジが改善または低下しているか | ラチェット方式で追跡 |
アンチパターン
1. カバレッジを品質の証明とみなす
「カバレッジが 90% あるので、十分にテストされています」という考え方は危険です。カバレッジが示すのはコードが実行されたことであり、動作が検証されたことではありません。対策: クリティカルなモジュールでは、割合とミューテーションスコアを併用してください。アサーションのないテストは、行カバレッジが 100% でもミューテーションスコアを下げるべきです。
2. 数値を水増しするためにファイルを除外する
テストが難しいファイル(エラーハンドラー、統合モジュール)を除外リストに追加すると、最も重要な不足が隠れてしまいます。対策: 生成ファイル、型定義、バレルエクスポートなど、本当にテスト不可能なコードのみを除外し、各除外の根拠を文書に記録してください。
3. 目標達成のために単純なテストを書く
it("should exist", () => expect(MyClass).toBeDefined()) は、価値を加えずにカバレッジを増やします。対策: すべてのテストで、壊れた場合にユーザーへ影響する動作を検証してください。ミューテーションテストにより、このような何もしないテストを検出できます。
4. モジュールごとの分析をせずに全体のしきい値を設定する
決済のカバレッジが 30% でも、ユーティリティが平均を押し上げれば、全体のしきい値 80% は達成できてしまいます。対策: 決済/認証については、ディレクトリごとのしきい値を 90% 以上に設定してください。
5. カバレッジのしきい値を一度設定したまま、調整しない
6か月間78%のまま停滞しているチームは、改善できていません。**対処法:**ラチェットで下限を自動的に引き上げ、四半期ごとに見直し、停滞の原因を調査します。
6. ブランチカバレッジを無視する
const r = cond ? a : b では、片方のブランチが一度も実行されなくても、行カバレッジは100%と報告されます。対処法:lines とともに branches も必ず報告し、ゲートの対象にします。
7. E2E テストのみでカバレッジを測定する
1つの E2E テストがエッジケースを一切テストせずにコードベースの60%に触れているだけでは、広範囲でも浅いテストです。**対処法:**ユニット/統合テストのカバレッジを E2E とは別に測定し、ユニット/統合テストの合計をゲートの対象にします。E2E カバレッジは補足的な指標であり、ゲートにはしません。
検証
リグレッション時にゲートが実際に失敗することを確認してください。強制力のないグリーンビルドには価値がありません。
- **しきい値が作動すること:**コミット済みの指標を一時的に現在の値より下げる(または成功しているテストを1つ削除する)かして、
npm run test:coverage(またはpytest --cov=src --cov-fail-under=80)を実行し、ゼロ以外の終了コードを確認します。その後、元に戻します。 - ラチェットが作動すること:
.coverage-ratchet.jsonがコミットされた状態で、テストを削除してカバレッジを低下させ、ラチェットスクリプトを実行します。FAIL: ... coverage droppedと出力され、終了コードが1になることを確認します。 - **ブランチゲートが有効であること:**レポートに
branches列が表示され、しきい値設定にbranchesが含まれていることを確認します。linesだけでは不十分です。 - **PR の差分が表示されること:**ソースファイルを1つ変更するドラフト PR を作成し、変更されたファイルの数値を含むカバレッジ差分コメントが投稿されることを確認します。
手順1でカバレッジを下げた後に終了コードが0になる場合、ゲートが正しく組み込まれていません。「完了」とする前に修正してください。
完了条件
- CI でプッシュのたびにカバレッジが自動的に実行される(レポート生成に手動の手順が不要)。
- CI でカバレッジのしきい値が強制される。行カバレッジまたはブランチカバレッジが定義済みの最小値を下回ると、ビルドがゼロ以外で終了する(「検証」手順1で確認済み)。
- カバレッジレポートが CI アーティファクト(HTML +
json-summary)として公開され、PR ごとのカバレッジ差分が PR コメントに投稿される。 - カバレッジ設定に除外リストとディレクトリごとのしきい値(支払い/認証では90%以上)が含まれており、CONTRIBUTING/カバレッジのドキュメントに各除外の理由が記載されている。
- ラチェットが組み込まれている:
.coverage-ratchet.jsonがコミットされ、ラチェットスクリプトが CI で実行され、記録済みのベースラインからのリグレッションでビルドが失敗する(「検証」手順2で確認済み)。
参考ファイル(references/ 内)
- tool-config.md — Vitest(
@vitest/coverage-v8/-istanbul)、Vitest 以外のランナー用 c8 CLI、nyc、Jest、coverage.py の完全なプロバイダー設定と、インストールおよび実行コマンド。 - ci-gating.md — PR カバレッジ差分のワークフロー、全体/ディレクトリごと/ファイルごとのしきい値、および
coverage-ratchet.tsスクリプト。 - mutation-testing.md — アサーションの品質を測定するための Stryker(
stryker.config.json、増分実行)および mutmut の設定。
関連スキル
- unit-testing — カバレッジを高めるテストの作成:モック戦略、フレームワーク固有の設定、CI で変更ファイルのみを対象にカバレッジを測定する Vitest の
coverage.changed。テストを作成する場合はこちらを参照してください。このスキルではテストを測定し、ゲートを設定します。 - ci-cd-integration — カバレッジゲート、アーティファクトの保存、PR コメントのパイプラインへの組み込み。
- qa-metrics — ミューテーションスコアや欠陥流出率とともに、追跡対象の KPI としてカバレッジの推移を長期的に確認します。ダッシュボードや推移についてはそちらを参照してください。このスキルはリポジトリごとの設定とゲートに関するものです。
- ai-qa-review — テストが不足しているパスの AI 支援による特定と、コンポーネントカバレッジの同等性を実現する Vitest のブラウザーモード。