JaCoCoを使ったJavaテストカバレッジの完全ガイド

Javaアプリケーションのテストを増やしても、「重要な分岐まで通っているか」「変更した処理が一度も実行されていないまま残っていないか」は、テスト件数だけでは判断できません。JaCoCoは、テスト実行中にJavaバイトコードのどこが実行されたかを収集し、命令・分岐・行などのカバレッジとして可視化するツールです。

ただし、カバレッジ率が高いことと、テストの品質が高いことは同じではありません。アサーションが弱いテスト、境界値を確かめないテスト、誤った仕様を固定するテストでも、コードを通過すればカバレッジは上がります。本記事では、JaCoCoをMavenまたはGradleへ組み込み、レポート作成、しきい値検証、CI成果物、複数モジュールまで運用する方法を、公式ドキュメントに基づいて整理します。

目次

JaCoCoが測るものと、測れないもの

JaCoCoは、クラスロード時にJavaエージェントでクラスを計測可能な形へ変換し、実行された箇所を記録します。集めた実行データと、レポート生成時に渡すクラスファイル・ソースファイルを対応付けることで、人が読めるHTMLレポートや機械処理向けのXML、CSVを生成します。

指標JaCoCoでの意味実務での見方
命令カバレッジ実行済み、または未実行のJavaバイトコード命令を数えるソースの改行や整形に左右されにくく、全体傾向の比較に向く
分岐カバレッジif文とswitch文の分岐のうち、実行された経路を数えるtrue側だけでなくfalse側、各caseが試されているかを見る
行カバレッジ行に割り当てられた命令が一つでも実行されると、その行は実行済みになる読みやすい一方、一行の一部だけを通った場合があるため色と分岐も確認する

命令カバレッジはC0、分岐カバレッジはC1とも呼ばれます。分岐カバレッジはifとswitchを対象にしますが、JaCoCoの定義では例外処理は分岐として数えません。また、行カバレッジを表示するには、クラスファイルに行番号のデバッグ情報が必要です。赤・黄・緑の色分けだけを眺めるのではなく、命令、分岐、未実行の複雑度を組み合わせて読むことが重要です。

重要: JaCoCoが示すのは「テスト実行中にコードが通ったか」です。期待値が正しいか、要件を満たすか、障害を防げるかは別途レビューし、アサーション、境界値、異常系、結合テストで確かめます。

導入前にそろえる前提条件

まず、テストがローカルとCIで安定して再現できる状態を作ります。カバレッジ導入と同時に不安定なテストやビルド構成まで変更すると、失敗原因を切り分けにくくなります。最初の一回はしきい値を設定せずにレポートだけを生成し、対象クラス、実行データ、出力先が想定どおりか確認するのが安全です。

  • バージョンを固定する: Mavenではプラグインのバージョン、GradleではtoolVersionを明記します。本記事の設定例は、JaCoCo公式変更履歴で2026年6月4日の正式リリースと確認できる0.8.15を固定した例です。SNAPSHOTは通常のCIへ持ち込まず、更新時は変更履歴と対応するJavaクラスファイル版を確認します。
  • デバッグ情報を残す: 行番号とソースのハイライトを使うなら、デバッグ情報を含むクラスファイルを生成します。命令と分岐はデバッグ情報がなくても計測できますが、行単位の調査性が下がります。
  • テスト対象を決める: 単体テストと結合テストを同じ実行データにまとめるのか、別レポートにするのかを先に決めます。比較する期間ごとに対象範囲が変わると、率の増減を正しく説明できません。
  • 再現可能な成果物を使う: テスト実行時とレポート生成時で、同じクラスファイルを使います。再コンパイルや後処理によってバイト列が変わると、実行済みでも0%と表示されることがあります。

導入前の基準値として、全体の行・分岐カバレッジだけでなく、主要パッケージと変更頻度の高いクラスも記録します。全体平均だけでは、よく変更される重要ロジックが未テストでも、単純なコードの大量計測によって隠れるためです。

Mavenでレポート生成とverify判定を組み込む

Mavenでは、prepare-agentでテストJVMにJaCoCoエージェントの引数を渡し、reportでレポートを作り、checkで基準を検証します。prepare-agentは既定でinitialize、reportとcheckは既定でverifyフェーズに結び付くゴールです。設定を明示しておくと、チーム内で実行順序を読み取りやすくなります。

<properties>
  <jacoco.version>0.8.15</jacoco.version>
</properties>

<build>
  <plugins>
    <plugin>
      <groupId>org.jacoco</groupId>
      <artifactId>jacoco-maven-plugin</artifactId>
      <version>${jacoco.version}</version>
      <executions>
        <execution>
          <id>coverage-agent</id>
          <phase>initialize</phase>
          <goals>
            <goal>prepare-agent</goal>
          </goals>
        </execution>
        <execution>
          <id>coverage-report</id>
          <phase>verify</phase>
          <goals>
            <goal>report</goal>
          </goals>
        </execution>
        <execution>
          <id>coverage-check</id>
          <phase>verify</phase>
          <goals>
            <goal>check</goal>
          </goals>
          <configuration>
            <rules>
              <rule>
                <element>BUNDLE</element>
                <limits>
                  <limit>
                    <counter>LINE</counter>
                    <value>COVEREDRATIO</value>
                    <minimum>0.70</minimum>
                  </limit>
                  <limit>
                    <counter>BRANCH</counter>
                    <value>COVEREDRATIO</value>
                    <minimum>0.60</minimum>
                  </limit>
                </limits>
              </rule>
            </rules>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

上の70%と60%は設定方法を示す例であり、すべてのプロジェクトに適した推奨値ではありません。実際には現在値を測り、重要度と改善可能性を踏まえて決めます。実行は次の一行です。Mavenでは指定したフェーズまで前段のフェーズが順に実行されるため、verifyを呼べばテスト、レポート生成、基準判定まで進みます。

mvn clean verify

通常の単一モジュールではHTMLレポートがtarget/site/jacoco/index.html、実行データがtarget/jacoco.execに生成されます。reportはHTML、XML、CSVを生成できます。CIの人向け閲覧にはHTML、別ツールへ渡す用途にはXMLを残すと扱いやすくなります。

すでにmaven-surefire-pluginのargLineへメモリ設定などを書いている場合は、JaCoCoが用意する値を上書きしないよう、公式ドキュメントにある遅延評価の@{argLine}を既存オプションの前へ含めます。また、SurefireやFailsafeでforkCountを0にするとテストJVMへJavaエージェントが付かず、カバレッジを記録できません。結合テストを分ける構成では、prepare-agent-integrationとreport-integrationを使い、単体テスト用の実行データと混同しない設計も検討します。

Gradleでレポートと検証タスクを接続する

Gradleではjavaとjacocoプラグインを適用すると、test用にjacocoTestReportとjacocoTestCoverageVerificationが追加されます。ただし、公式ドキュメントではjacocoTestReportはtestへ自動依存せず、jacocoTestCoverageVerificationもJavaプラグインのcheckへ自動では組み込まれないと説明されています。CIで実行漏れを起こさないため、依存関係を明示します。

plugins {
    java
    jacoco
}

jacoco {
    toolVersion = "0.8.15"
}

tasks.test {
    useJUnitPlatform()
    finalizedBy(tasks.jacocoTestReport)
}

tasks.jacocoTestReport {
    dependsOn(tasks.test)
    reports {
        html.required.set(true)
        xml.required.set(true)
        csv.required.set(false)
    }
}

tasks.jacocoTestCoverageVerification {
    dependsOn(tasks.test)
    violationRules {
        rule {
            limit {
                counter = "LINE"
                value = "COVEREDRATIO"
                minimum = "0.70".toBigDecimal()
            }
            limit {
                counter = "BRANCH"
                value = "COVEREDRATIO"
                minimum = "0.60".toBigDecimal()
            }
        }
    }
}

tasks.check {
    dependsOn(tasks.jacocoTestCoverageVerification)
}

これはKotlin DSLの例です。レポートを単独で作るなら./gradlew clean test jacocoTestReport、基準判定まで含めるなら./gradlew clean check jacocoTestReportを実行します。既定のHTMLレポートはbuild/reports/jacoco/test配下です。testの後に必ずレポートを作りたい場合はfinalizedBy、レポートからテスト実行を要求したい場合はdependsOnという役割を理解して設定します。

注意: Gradleの検証タスクは違反した最初のルールだけを報告します。一度の失敗表示に全問題が並ぶとは限らないため、直した後に再実行して残りのルールも確認します。

HTML・XMLレポートを正しく読む

HTMLレポートは、最上位のバンドルからパッケージ、クラス、ソース行へ掘り下げて読みます。最初に全体率だけを見るのではなく、未実行命令が多いパッケージ、分岐率が低いクラス、変更頻度が高いクラスを絞り込みます。ソース表示では、赤が未実行、黄が一部実行、緑が実行済みを示します。分岐がある行にはダイヤ形の表示が付き、分岐の未実行・一部実行・全実行を確認できます。

  1. まずテスト対象のパッケージとクラスがレポートに含まれているか確認します。
  2. Missed InstructionsとMissed Branchesが多いクラスを開きます。
  3. 黄色の行では、同じ行にある条件式のどの経路が未実行かテストケースへ戻って確認します。
  4. 複雑度が高く未実行分岐も多いメソッドを、追加テストまたは設計見直しの候補にします。
  5. XMLはCIや集計ツール用、HTMLは人による原因調査用として、同じビルドから生成したものを保存します。

行カバレッジには注意点があります。一行に複数の式が書かれていると、その行へ割り当てられた命令の一部だけを実行しても行自体は実行済みになります。したがって、行率だけを合格条件にせず、条件分岐を持つ業務ロジックでは分岐率も併用します。逆に、命令率はソースの整形に影響されにくいものの、開発者には直感的でないため、レビューでは行・分岐と一緒に示すと判断しやすくなります。

除外設定としきい値を数字合わせにしない

除外は、生成コード、外部から取り込んだコード、計測すると技術的な衝突が起きる特殊なクラスなど、理由を説明できる対象に限定します。「テストしにくい」「率が下がる」という理由だけで業務ロジックを除外すると、レポートの意味が失われます。JaCoCoのprepare-agentにある除外は主に計測時の性能最適化や技術的な例外向けであり、レポートから除外したいだけならreport側で設定するのが公式の案内です。

Mavenではreportとcheck、GradleではJacocoReportとJacocoCoverageVerificationで同じ対象範囲になるようにします。表示レポートでは除外されているのに検証では含まれる、またはその逆になると、画面上の率とビルド判定が一致しません。除外パターン、理由、承認者、見直し日をリポジトリ内へ記録し、パターンが広すぎないか定期的に確認します。

  • 初期値は実測から決める: 現状が62%なら、いきなり90%で全開発を止めるのではなく、まず低下を防ぐ水準を置きます。
  • 全体と重要箇所を分ける: バンドル全体の下限に加え、決済、権限、計算など重要パッケージには別のルールを検討します。
  • 行と分岐を併用する: 行だけを通すテストに偏らず、条件の両側やswitchの各経路を評価します。
  • 率だけを目標にしない: 未実行の重要経路、欠けている異常系、弱いアサーションをレビュー項目として残します。

CIではレポートと再現材料を成果物にする

CIでは、Mavenならmvn clean verify、Gradleなら./gradlew clean check jacocoTestReportを基本コマンドにし、テストとカバレッジ判定を同じビルド内で行います。成功時だけでなく失敗時にも調査できるよう、CIサービスの後処理機能でテスト結果、JaCoCo HTML、XML、実行ログを保存します。HTMLにソースが含まれる場合は、公開範囲をリポジトリの機密性に合わせます。

成果物用途注意点
HTMLレポート開発者が未実行行と分岐を調べるソース表示を含むため公開権限を確認する
XMLレポートCIの可視化や後続処理へ渡す同じビルドのHTML・実行結果と対応付ける
exec実行データ再集計や集約の入力単体では行やメソッドの意味を持たず、対応するクラスが必要
テスト結果とログ未生成・0%・基準違反の原因を調べるテストが実際に走ったか、エージェント引数が渡ったかを確認する

JaCoCoはクラスIDで実行データと解析対象クラスを結び付けます。そのため、別ジョブでソースから再コンパイルしたクラスを使ってレポートだけ作り直すと、同じコードに見えてもコンパイラ版や設定の違いでIDが変わる可能性があります。分離したCIジョブで集約する場合は、テストに使った実行データと、同じビルドで生成したクラスファイルを一組として受け渡します。

カバレッジ低下でビルドを止める場合は、失敗理由がログから判別できるようにします。テスト失敗、レポート未生成、しきい値違反を同じ「品質ゲート失敗」とだけ表示すると復旧が遅れます。ジョブ名やステップを分け、失敗したコマンドと生成済み成果物を残すと、担当者が数字の問題か計測の問題かをすぐ切り分けられます。

複数モジュールのカバレッジを集約する

Mavenの複数モジュール構成では、各モジュールの率を単純平均するのではなく、report-aggregateで実行データとクラス・ソースを集約します。report-aggregateは、集約用プロジェクトが依存するリアクター内モジュールから情報を集めます。compile、runtime、providedスコープの依存モジュールはソース、クラス、実行データが対象になり、testスコープの依存は実行データだけが対象です。結合テスト専用モジュールをtestスコープで参加させる構成にも利用できます。

集約用モジュールを一つ設け、対象モジュールへの依存関係を明示し、リアクター全体をverifyまで実行してから集約レポートを生成します。実行順序だけに頼るのではなく、依存関係と出力ファイルの場所を確認します。集約対象に同名クラスの異なる版が混ざると解析できないため、重複した成果物や古いビルド出力をcleanで除去します。

Gradleの複数プロジェクトではjacoco-report-aggregationプラグインを使う方法があります。このプラグインは、JVM Test Suiteと組み合わせ、依存プロジェクトが公開するJaCoCo結果を集約します。JavaプラグインはJVM Test Suiteを自動適用します。アプリケーション側から依存関係をたどって集約する構成と、集約専用プロジェクトで対象を宣言する構成を選べます。テストスイート名が異なる場合は、どのスイートを集めるかを明示します。

集約時の原則: モジュール別パーセントの平均ではなく、対象全体のcoveredとmissedの実数から率を計算します。10行のモジュールと1万行のモジュールを同じ重みで平均すると、実態と異なる結果になります。

よくあるトラブルと切り分け方

レポートが0%、または実行したクラスが未計測になる

最初にテストが実行されたか、execファイルが作られたか、HTMLレポート右上のSessionsに対象クラスがあるかを確認します。Sessionsにはあるのにレポートへのリンクがない場合、実行時と解析時のクラスID不一致が疑われます。テスト後の再コンパイル、異なるJDKやコンパイラ設定、難読化、別のJavaエージェント、特殊なクラスローダーによる変更を確認し、テストで使ったものと同一のクラスファイルでレポートを作ります。

Mavenでjacoco.execが生成されない

prepare-agentが実行されたか、Surefire/FailsafeのargLineがJaCoCoの値を上書きしていないか、forkCountが0になっていないか、テストがスキップされていないかを確認します。独自のJVM引数が必要なら@{argLine}を含めます。並列ジョブが同じdestFileへ書く構成では、ジョブごとに出力先を分けてからmergeまたは集約し、上書きや取り違えを避けます。

行カバレッジやソース色分けが表示されない

クラスファイルに行番号のデバッグ情報が含まれるか、レポート生成時に正しいソースディレクトリを渡しているかを確認します。JaCoCoのFAQでは、ソースフォルダーはJavaパッケージを表すディレクトリの直接の親である必要があると説明されています。生成ソースは、コンパイルに使った実体とレポートへ渡すパスを一致させます。

Gradleでレポートが古い、checkだけでは生成されない

jacocoTestReportがtestへ依存しているか、checkがjacocoTestCoverageVerificationへ依存しているかを確認します。古い実行データが残る場合はcleanから再実行し、対象のTestタスクとexecutionDataの対応を確認します。テスト失敗で後続タスクが止まるCIでは、失敗時の成果物回収方法も別途設定します。

実務導入チェックリストと公式資料

段階的に定着させる手順

  1. プラグイン版を固定し、ローカルでテストとHTMLレポート生成を成功させます。
  2. 対象外にするコードを最小限に決め、理由を文書化します。
  3. 行、分岐、命令の現在値と、重要パッケージの未実行箇所を記録します。
  4. CIへHTML、XML、テスト結果の保存を追加し、失敗時にも調査できるか確認します。
  5. まず現状悪化を防ぐしきい値を設定し、改善とともに小刻みに引き上げます。
  6. 高カバレッジでも、アサーション、境界値、異常系、仕様レビューを継続します。
  7. 複数モジュールでは集約対象、テストスイート、クラス成果物の対応を定期的に監査します。

JaCoCoを有効にするだけでは、テストは良くなりません。価値があるのは、未実行の重要経路を見つけ、テスト追加や設計改善へ結び付け、同じ基準で継続的に比較できる状態を作ることです。最初から100%を目指すより、対象を安定させ、結果を再現可能にし、重要な分岐から改善するほうが、数字と実際のリスクを結び付けやすくなります。

公式資料

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次