覆盖率 = 测试跑到了多少代码。它不能证明"代码都对",但能告诉你哪里没测到——是找测试盲区的雷达。这篇讲 JaCoCo(Java 最常用的覆盖率工具)怎么配置、报告怎么看、覆盖率怎么提。
验证说明:本文示例已用 Maven 项目实跑验证(IDEA 内置 Maven + jacoco-maven-plugin 0.8.12)——文章里的
PriceService+ 参数化测试跑完,指令/分支/行覆盖率均 100%,check 门禁通过,报告生成于target/site/jacoco/index.html。
一、覆盖率是什么
| 覆盖类型 | 含义 | 通俗理解 |
|---|---|---|
| 行覆盖(Line) | 多少行代码被执行过 | 每行代码跑没跑到 |
| 分支覆盖(Branch) | if/else 的每个分支是否都走过 | 判断的"是/否"都测了吗 |
| 方法覆盖(Method) | 多少方法被调用过 | 方法进没进过 |
| 类覆盖(Class) | 多少类被加载过 | 类测没测到 |
关键:行覆盖 ≠ 分支覆盖——if (a && b) 这一行跑了,但 a=false 的分支可能没测。重点看分支覆盖。
二、JaCoCo 配置(Maven)
pom.xml 加插件(test 阶段自动生成覆盖率报告):
xml
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<!-- 测试前埋点 -->
<execution>
<goals><goal>prepare-agent</goal></goals>
</execution>
<!-- 测试后生成报告 -->
<execution>
<id>report</id>
<phase>test</phase>
<goals><goal>report</goal></goals>
</execution>
<!-- 覆盖率不达标构建失败(质量门禁) -->
<execution>
<id>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.90</minimum> <!-- 行覆盖 90% -->
</limit>
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum> <!-- 分支覆盖 80% -->
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>bash
mvn test # 跑测试 + 生成报告
# 报告在 target/site/jacoco/index.html实测结果(本文示例项目):mvn test verify 后 jacoco.csv 显示 PriceService 行覆盖 5/5、分支 6/6、指令 28/28——全部 100%,check 门禁(行≥90%、分支≥80%)通过。
check 规则让覆盖率不达标构建失败——把覆盖率变成"质量门禁"(低于 90% 不让合并代码,和 CI 流水线配合见《CI/CD 快速入门》)。
三、报告怎么看
打开 target/site/jacoco/index.html:
| 颜色 | 含义 |
|---|---|
| 🟢 绿 | 已覆盖 |
| 🔴 红 | 未覆盖(重点看这里) |
| 🟡 黄 | 部分覆盖 |
怎么看:
- 先看包/类列表,找红色(未测的类)
- 点进类,红色行 = 没执行的代码,黄色菱形 = if 只测了一个分支
- 重点补:核心业务逻辑(Service 层)、异常分支(catch)、边界条件(if 的分支)
四、覆盖率怎么提(实操手法)
java
// 假设被测方法
public class PriceService {
public double calc(double price, int discountType) {
if (discountType == 1) return price * 0.9; // 分支 1
if (discountType == 2) return price * 0.8; // 分支 2
if (price < 0) throw new IllegalArgumentException(); // 异常分支
return price; // 默认分支
}
}用参数化测试一次覆盖全部分支(《JUnit 5 进阶》的 @CsvSource 正是干这个的):
java
@ParameterizedTest
@CsvSource({
"100, 1, 90", // 分支 1
"100, 2, 80", // 分支 2
"-1, 0, EXCEPTION", // 异常分支
"100, 0, 100" // 默认分支
})
void 折扣计算(double price, int type, double expected) { ... }提高覆盖率的套路(按性价比排序):
- 分支全覆盖:if/else 每个分支 + 异常分支都测(参数化测试最省力)
- 边界值:0、负数、最大值、空值、null
- 先测核心业务(Service),再补工具类
- 别为覆盖率写废测试:只断言不验证逻辑的测试是"注水"——覆盖率数字上去了,但没实际价值(反而误导)
五、常见坑
- 只追行覆盖:行覆盖 90% 但分支 50%——if 只测了一半分支(重点看 BRANCH)
- 给 getter/setter 凑覆盖率:没意义,JaCoCo 可配置排除(
excludes) - 覆盖率 100% 的幻觉:覆盖 ≠ 正确,
assertEquals(1, 1)也算覆盖——覆盖率 + 断言质量一起看 - CI 里没配 check:本地测了,CI 不强制,覆盖率慢慢掉——在流水线加 check 规则
xml
<!-- 排除样板代码(getter/setter/配置类)不参与统计 -->
<configuration>
<excludes>
<exclude>**/dto/**</exclude>
<exclude>**/config/**</exclude>
<exclude>**/*Application.class</exclude>
</excludes>
</configuration>小结
- 覆盖率 = 测试盲区雷达:行覆盖 + 分支覆盖一起看
- JaCoCo:
prepare-agent(埋点)+report(报告)+check(门禁) - 报告红绿看盲区,黄色菱形 = 分支没测全
- 提高覆盖率:参数化测试覆盖分支 + 边界值 + 先测核心业务
- 别注水:覆盖率数字 + 断言质量一起看(配 CI check 防止回退)
