共计 2088 个字符,预计需要花费 6 分钟才能阅读完成。
线上事故引发的思考
去年参与的一个电商项目中,我们遇到了一个典型的单元测试缺陷案例。由于支付服务接口没有进行 Mock,导致每次运行单元测试都会真实调用第三方支付平台。在持续集成环境中,这个测试用例反复执行产生了数百条测试支付记录,最终触发了支付平台的风控机制,导致生产环境支付功能被临时封锁。这个事故让我们深刻意识到:没有隔离外部依赖的单元测试,本身就是安全隐患。

JUnit 5 的新世界
相比 JUnit 4,JUnit 5 带来的不仅是 API 变化,更是测试思维的升级:
- 架构变革 :将 Runner 机制改为扩展模型 (Extension API),支持更多定制可能
- 嵌套测试 :通过 @Nested 实现测试用例的层次化组织,符合 BDD 风格
- 动态测试 :用 @TestFactory 生成运行时决定的测试用例,适合数据驱动测试
- 参数化测试 :@ParameterizedTest 支持 8 种参数源,比 JUnit 4 的 @Parameters 更灵活
基础篇:JUnit 5 核心机制
生命周期钩子
- @BeforeAll/@AfterAll:静态方法,整个测试类执行前后各一次
- @BeforeEach/@AfterEach:每个 @Test 方法执行前后都会运行
断言体系
JUnit 5 的断言支持异常断言和超时控制:
@Test
void shouldThrowException() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Integer.valueOf(null)
);
assertEquals("null", exception.getMessage());
}
进阶篇:Mockito 深度技巧
参数匹配器
Mockito 的 argThat 可以自定义匹配逻辑:
when(userRepo.find(argThat(user -> user.getAge() > 18)))
.thenReturn(Collections.emptyList());
验证模式
// 验证方法在 100ms 内被调用 2 次
verify(mockService, timeout(100).times(2)).process(any());
Spy 应用场景
当需要监控真实对象的部分行为时:
List<String> realList = new ArrayList<>();
List<String> spyList = spy(realList);
// 只对 size() 方法进行 mock
when(spyList.size()).thenReturn(100);
集成篇:Spring 测试切片
Spring Boot 2.4+ 提供了精细化的测试注解:
- @WebMvcTest:只加载 Web 层组件
- @DataJpaTest:仅初始化 JPA 相关配置
- @JsonTest:专注 JSON 序列化测试
示例:
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
MockMvc mockMvc;
@MockBean
UserService userService;
@Test
void getUserById() throws Exception {given(userService.findById(1L))
.willReturn(new User(1L, "test"));
mockMvc.perform(get("/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("test"));
}
}
生产环境避坑指南
DRY 原则实践
- 使用 @BeforeEach 初始化通用测试数据
- 对重复的验证逻辑封装为自定义断言
- 提取测试数据生成器到独立工厂类
Mock 使用准则
- 只 Mock 跨进程调用(数据库 / 微服务 / 中间件)
- 不 Mock 同一模块内的领域对象
- 避免验证非业务关键方法的调用
并行化配置
在 Maven 的 surefire 插件中启用并行执行:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<parallel>methods</parallel>
<threadCount>4</threadCount>
</configuration>
</plugin>
思考题:领域事件测试
领域驱动设计中的领域事件测试需要考虑:
- 事件是否被正确发布(次数、内容)
- 事件处理器的幂等性保证
- 事件时序的验证策略
参考答案方向:
- 使用 Mockito 的 InOrder 验证事件发布顺序
- 通过自定义 EventCollector 收集所有发布的事件
- 对事件处理器进行边界条件测试(空事件、重复事件等)
单元测试不是银弹,但好的测试习惯就像代码的安全带。当你在深夜部署代码时,那些绿色的测试结果会给足你信心。记住:测试代码的质量与业务代码同等重要,因为它们共同构成了系统的可维护性基石。
正文完
