概要
テストを書いていると「カバレッジ何パーセントを目指すべきか」という議論に必ず遭遇します。しかし、カバレッジは「テストが通過したコードの割合」を示すだけで、テストの検証内容が正しいかどうかは教えてくれません。行カバレッジが 100% でも、分岐の片側しか通っていなかったり、アサーションが甘かったりすれば、バグはすり抜けます。この記事では、Maven プロジェクトに JaCoCo を導入してカバレッジを測定する手順を示したうえで、行カバレッジ・分岐カバレッジ・命令カバレッジそれぞれが何を測っているかの違いを具体的なコード例で説明します。カバレッジ率の現実的な目標設定、カバレッジだけに依存しないテスト品質の考え方にも触れます。
使いどころ
CI パイプラインに JaCoCo を組み込み、カバレッジが一定値を下回ったらビルドを失敗させるゲートを設定する
レガシーコードのテスト拡充で現状カバレッジを可視化し、優先的にテストすべき箇所を特定する
コードレビューでカバレッジレポートを参照し、分岐カバレッジが低い条件分岐のテスト追加を依頼する
プルリクエスト単位で新規追加コードのカバレッジを確認し、既存負債と切り分けて管理する
重要ロジックだけ PITest などと組み合わせて、単純な通過ではなく検証強度も見直す
コード例
import java.math.BigDecimal;
import java.math.RoundingMode;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.*;
class DiscountServiceTest {
static class DiscountService {
BigDecimal calculateDiscount(String rank, BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) <= 0)
throw new IllegalArgumentException("購入金額は正の値を指定してください");
return switch (rank) {
case "GOLD" -> amount.multiply(new BigDecimal("0.10"))
.setScale(0, RoundingMode.FLOOR).min(new BigDecimal("5000"));
case "SILVER" -> amount.multiply(new BigDecimal("0.05"))
.setScale(0, RoundingMode.FLOOR).min(new BigDecimal("2000"));
case "BRONZE" -> BigDecimal.ZERO;
default -> throw new IllegalArgumentException("不明なランク: " + rank);
};
}
}
private DiscountService service;
@BeforeEach void setUp() { service = new DiscountService(); }
@Test @DisplayName("GOLD 10%割引")
void gold10percent() {
assertEquals(new BigDecimal("1000"), service.calculateDiscount("GOLD", new BigDecimal("10000")));
}
@Test @DisplayName("GOLD 上限5000円")
void goldCap() {
assertEquals(new BigDecimal("5000"), service.calculateDiscount("GOLD", new BigDecimal("100000")));
}
@ParameterizedTest
@CsvSource({"10000,500", "40000,2000", "80000,2000"})
@DisplayName("SILVER 5%割引(上限2000円)")
void silver(String amount, String expected) {
assertEquals(new BigDecimal(expected), service.calculateDiscount("SILVER", new BigDecimal(amount)));
}
@Test @DisplayName("BRONZE は割引なし")
void bronze() { assertEquals(BigDecimal.ZERO, service.calculateDiscount("BRONZE", new BigDecimal("50000"))); }
@Test @DisplayName("不明ランクで例外")
void unknownRank() { assertThrows(IllegalArgumentException.class, () -> service.calculateDiscount("PLATINUM", new BigDecimal("10000"))); }
@Test @DisplayName("金額0以下で例外")
void zeroAmount() { assertThrows(IllegalArgumentException.class, () -> service.calculateDiscount("GOLD", BigDecimal.ZERO)); }
}Version Coverage
sealed class や record のカバレッジには JaCoCo 0.8.8 以上が必要。
// Java 17: switch 式で分岐を整理
public String classifyAge(int age) {
if (age < 0) throw new IllegalArgumentException("負の年齢");
return switch ((int)(age / 18)) {
case 0 -> "未成年";
default -> age < 65 ? "成人" : "高齢者";
};
}Library Comparison
注意点
行カバレッジ 100% は分岐カバレッジ 100% を意味しない。if-else の片方だけでも行カバレッジは高く出る
JaCoCo はバイトコード計装方式のため、新しい構文で正確に出ないバージョンがある。0.8.8 以降を推奨
カバレッジ率をノルマにするとアサーションなしの意味のないテストが量産されるリスクがある
Lombok 生成コードもカバレッジ対象になる。除外設定を入れないと数値が下がる
マルチモジュールでは jacoco:report-aggregate で統合レポートを生成する
例外系や境界値のテストが薄いまま数値だけ高く見えることがある。レポートは未通過箇所の発見に使い、品質判断は別軸で行う
カバレッジ閾値を厳しくしすぎると、変更のたびに無理なテスト追加が発生する。チームの開発速度と負債状況に合わせて設定する
FAQ
新規コードは 80% 以上を目安にするチームが多いです。数値を絶対視せず、重要なロジックの分岐カバレッジを優先的に確認します。
Maven プラグインの excludes 設定で除外パターンを指定できます。DTO や自動生成コードを除外するのが一般的です。
アサーション不足、境界値の未テスト、異常系の未テストが主な原因です。カバレッジは通過の有無しか見ません。
条件分岐が多い業務ロジックでは分岐カバレッジの方が有用です。ただし、それだけで十分ではないため、重要な分岐に対する具体的なアサーションも合わせて確認します。
既存負債が大きいプロジェクトでは、まず差分カバレッジを管理する方が現実的です。全体値は長期的な改善指標として追うと運用しやすくなります。