关注我的公众号:【编程朝花夕拾】,可获取首发内容。
01 引言
之前看到很多文章提到代码覆盖率的问题,但是待在小公司,这些问题总是被弱化,也没有真正关注过到代码覆盖率的问题。代码覆盖率是衡量测试充分性的重要指标。它不能直接证明代码质量高,但覆盖率低,代码质量一定好不到哪去。
这两天看到一个框架用来测试代码覆盖率:JaCoCo,特地分享给大家。
02 简介
JaCoCo(Java Code Coverage Library)是一款开源的Java 代码覆盖率工具,主要用于检测单元测试对代码的覆盖程度 。它通过字节码插桩技术收集执行数据,生成可视化报告,帮助开发者识别未测试的代码路径,是 Java 生态中覆盖率检测的事实标准 。
JaCoCo是当前 Java 生态中最主流的开源代码覆盖率工具,由 EclEmma 团队开发维护,支持 Maven、Gradle、Ant 等构建工具,也能通过 Java Agent 技术在线监控运行中的JVM 程序。

GitHub地址:github.com/jacoco/jaco...
03 实战案例
3.1 Maven依赖
xml
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<!-- prepare-agent: 准备 JaCoCo agent -->
<!-- 实际效果等同于 JVM 参数: -javaagent:jacocoagent.jar -->
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<!-- report: 生成覆盖率报告 -->
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
该依赖需以插件的形式引入,用于编译期。
在配置中包含需要参数,如下:

其中参数中比较重要的参数:
prepare-agent插件运行的第一阶段,在 process-test-classes 阶段自动执行 。主要用来配置 JaCoCo 运行时 Agent,设置 JVM 参数,对测试类进行字节码插桩,以便收集覆盖数据report:插件运行的第二阶段,在 test 阶段之后执行,主要用来读取 jacoco.exec 数据,生成可读的覆盖率报告
3.2 业务用例
控制层用例
java
/**
* 计算器 REST 控制器 - 用于演示 JaCoCo 集成测试覆盖率
*/
@RestController
@RequestMapping("/api/calculator")
@RequiredArgsConstructor
public class CalculatorController {
private final CalculatorService calculatorService;
/**
* 加法
*/
@GetMapping("/add")
public Map<String, Object> add(@RequestParam int a, @RequestParam int b) {
int result = calculatorService.add(a, b);
return Map.of("operation", "add", "a", a, "b", b, "result", result);
}
/**
* 减法
*/
@GetMapping("/subtract")
public Map<String, Object> subtract(@RequestParam int a, @RequestParam int b) {
int result = calculatorService.subtract(a, b);
return Map.of("operation", "subtract", "a", a, "b", b, "result", result);
}
/**
* 除法
*/
@GetMapping("/divide")
public Map<String, Object> divide(@RequestParam int a, @RequestParam int b) {
double result = calculatorService.divide(a, b);
return Map.of("operation", "divide", "a", a, "b", b, "result", result);
}
/**
* 全局异常处理 - 捕获 IllegalArgumentException
*/
@ExceptionHandler(IllegalArgumentException.class)
public Map<String, Object> handleIllegalArgument(IllegalArgumentException e) {
return Map.of("error", true, "message", e.getMessage());
}
}
服务层用例
java
/**
* 计算器服务 - 用于演示 JaCoCo 代码覆盖率
*/
@Service
public class CalculatorService {
/**
* 加法
*/
public int add(int a, int b) {
return a + b;
}
/**
* 减法
*/
public int subtract(int a, int b) {
return a - b;
}
/**
* 除法(包含异常分支)
*/
public double divide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("除数不能为零");
}
return (double) a / b;
}
}
这些用例就是我们日常开发的业务。
3.3 测试用例
测试用例可以使用Spring容器也可以不使用。使用Spring容器调用比较简单,但是编译的时候使用的时间可能比较长。
测试用例需要通过Mock的方式调用,此处使用Spring容器的方式调用。
java
@SpringBootTest
@AutoConfigureMockMvc
@DisplayName("Calculator 集成测试 - Spring 容器模式")
class CalculatorIntegrationTest {
@Autowired
private MockMvc mockMvc;
// ===================== 基本运算 API =====================
@Nested
@DisplayName("基本运算 API 测试")
class BasicApi {
@Test
@DisplayName("加法 API -> /api/calculator/add")
void testAdd() throws Exception {
mockMvc.perform(get("/api/calculator/add")
.param("a", "10")
.param("b", "20"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.result", is(30)))
.andExpect(jsonPath("$.operation", is("add")));
}
@Test
@DisplayName("减法 API -> /api/calculator/subtract")
void testSubtract() throws Exception {
mockMvc.perform(get("/api/calculator/subtract")
.param("a", "30")
.param("b", "10"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.result", is(20)))
.andExpect(jsonPath("$.operation", is("subtract")));
}
@Test
@DisplayName("除法 API -> /api/calculator/divide (正常)")
void testDivide() throws Exception {
mockMvc.perform(get("/api/calculator/divide")
.param("a", "10")
.param("b", "4"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.result", is(2.5)))
.andExpect(jsonPath("$.operation", is("divide")));
}
}
// ===================== 全局异常处理器 =====================
@Nested
@DisplayName("全局异常处理测试")
class ExceptionHandler {
@Test
@DisplayName("除数为零 -> @ExceptionHandler 捕获 IllegalArgumentException")
void testDivideByZeroTriggersExceptionHandler() throws Exception {
mockMvc.perform(get("/api/calculator/divide")
.param("a", "10")
.param("b", "0"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.error", is(true)))
.andExpect(jsonPath("$.message", is("除数不能为零")));
}
}
}
测试的时候可以通过类分组,并使用@Nested标识。
3.4 编译执行
bash
mvn clean test
运行之后,可以在target看到每个包下面的测试报告,外层为整体的报告。

报告内容:

根据包名可以点击进入。
3.5 覆盖率报告解读
打开 JaCoCo 生成的 HTML 报告,你会看到以下颜色标识:
| 颜色 | 含义 |
|---|---|
| 🟢 绿色 | 完全覆盖 |
| 🟡 黄色 | 部分覆盖(如 if 分支只走了一边) |
| 🔴 红色 | 完全未覆盖 |
| ⚪ 无色 | 无可执行代码(如空行、注释) |
报告中的关键指标列:
- Missed Instructions / Total:未覆盖指令数 / 总指令数
- Missed Branches / Total:未覆盖分支数 / 总分支数
- Cxty:圈复杂度,值越高表示逻辑越复杂,测试难度越大
- Missed Lines / Total:未覆盖行数 / 总行数
- Missed Methods / Total:未覆盖方法数 / 总方法数
- Missed Classes / Total:未覆盖类数 / 总类数
实战解读技巧:
- 关注红色和黄色的方法,优先补充这些路径的测试用例
- 圈复杂度高的方法是高风险区域,即使覆盖了也要检查是否所有分支都走到
- 异常处理分支(catch 块)容易被遗漏,重点检查
04 小结
整体测试下来,如果测试用例编写详细,感觉就没有测试什么事了。但是开发的代码量也就上去了,不知道是不是真的有公司会严格使用呢?
老铁们,你们用没有被要求代码覆盖率需要达到多少才算达标?