JUnit5需配置junit-jupiter(含api/engine/params)、Platform和Vintage(兼容JUnit4);单元测试应避免滥用@SpringBootTest,优先用@ExtendWith(MockitoExtension.class)等轻量注解;JaCoCo低分支覆盖主因是未测if/else、异常路径等。

JUnit5依赖配置必须包含三个核心模块
只加 junit-jupiter-api 是不够的,运行时会报 NoClassDefFoundError: org.junit.platform.launcher.TestExecutionListener。JUnit5 实际由三部分组成:Platform(执行引擎)、Jupiter(编写接口)、Vintage(兼容旧版)。Spring Boot 2.2+ 默认已引入 Platform 和 Jupiter,但显式声明更可控。
推荐 Maven 配置(注意版本对齐):
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency>
这个 junit-jupiter 是 BOM 式聚合包,自动拉取 junit-jupiter-api、junit-jupiter-engine 和 junit-jupiter-params,避免手动配错版本。若项目含 JUnit4 遗留测试,再补一个 junit-vintage-engine 即可。
@SpringBootTest 不是默认选择,多数场景该用 @ExtendWith(MockitoExtension.class)
直接用 @SpringBootTest 启动完整上下文,测试类平均启动耗时 2–5 秒,且容易因 Bean 冲突或配置缺失失败。它只适合验证多组件协同逻辑(如 Controller → Service → Repository 链路),而非单个 Service 方法。
绝大多数单元测试应走轻量路径:
- Service 层测试:用
@ExtendWith(MockitoExtension.class)+@Mock/@InjectMocks模拟依赖,不加载 Spring 容器 - Repository 层测试:用
@DataJpaTest(仅加载 JPA 相关 Bean)+ 内存数据库(如 H2) - Controller 层测试:用
@WebMvcTest+@MockBean,跳过 Service 实现
例如一个纯业务逻辑的 OrderService.calculateDiscount(),Mock 掉 PriceRuleRepository 和 UserLevelService 即可,无需 Spring 上下文。
JaCoCo 报告里“行覆盖高但分支覆盖低”说明断言写得有问题
常见现象:JaCoCo 显示某方法行覆盖率 100%,但分支覆盖率只有 40%。典型原因是只测了主路径,没覆盖 if/else、switch case、Optional.isPresent() 等分支逻辑。
检查点:
- 每个
if (x != null)必须有x = null和x = new X()两组输入 - 使用
@ParameterizedTest+@ValueSource覆盖枚举值、布尔组合、边界值(如空字符串、负数、Long.MAX_VALUE) - 对抛异常路径,必须用
assertThrows<illegalargumentexception>(() -> method("invalid"))</illegalargumentexception>,不能只写method("invalid")后期待失败
示例:一个判断用户等级的方法
@ParameterizedTest
@CsvSource({"GOLD, true", "SILVER, false", "BRONZE, false", "null, false"})
void shouldCheckIfUserIsGold(String level, boolean expected) {
User user = new User();
user.setLevel(level);
assertEquals(expected, service.isGoldUser(user));
}
覆盖率数字本身没有意义,关键看哪些行被漏测
JaCoCo 报告中红色高亮的行,比整体百分比重要十倍。重点关注:
- 构造函数里未初始化的 final 字段赋值逻辑
- 日志语句(
log.debug("xxx"))——常因 log level 被跳过,实际未执行 - Lombok 生成的
toString()、equals()—— 若未显式调用,JaCoCo 会标为未覆盖 - try-catch 中的 catch 块 —— 很多人只 mock 正常返回,从不触发异常分支
真实项目中,80% 的“低覆盖”问题都出在对异常流、边界条件、日志和 Lombok 方法的忽视上,而不是业务主干逻辑。别盯着 75% 还是 82%,打开 HTML 报告,逐个点开红色行,问自己:“这段代码有没有可能不执行?如果不会,为什么存在?”

















