测试替身不是为了“把覆盖率刷高”,而是让测试隔离慢、随机或不可控制的外部依赖。先判断要验证结果还是协作协议,再 决定是否需要 Mockito。
Table of contents
四种常见测试替身
- **Stub(桩)**返回预先安排的数据,用于驱动被测代码走到目标分支。
- **Mock(模拟对象)**记录调用,用于验证重要的协作协议。
- **Fake(简化实现)**具有可工作的行为,例如内存仓库。
- **Spy(间谍)**包装真实对象并记录部分调用,容易造成难懂测试,应谨慎使用。
能用简单 Fake 清楚表达行为时,不必 Mock 每个依赖。
先写状态测试
interface 用户仓库 {
java.util.Optional<用户> 按编号查找(String id);
}
record 用户(String id, boolean active) {}
final class 登录服务 {
private final 用户仓库 repository;
登录服务(用户仓库 repository) { this.repository = repository; }
boolean 可以登录(String id) {
return repository.按编号查找(id).map(用户::active).orElse(false);
}
}
@org.junit.jupiter.api.Test
void 激活用户可以登录() {
用户仓库 fake = id -> java.util.Optional.of(new 用户(id, true));
org.junit.jupiter.api.Assertions.assertTrue(new 登录服务(fake).可以登录("U-1"));
}
这个 Fake 是一个 Lambda,没有框架成本,测试只关心业务结果。
只验证有意义的交互
发送通知属于外部可观察行为,可以使用 Mock:
var notifier = org.mockito.Mockito.mock(通知器.class);
var service = new 注册服务(notifier);
service.注册("user@example.com");
org.mockito.Mockito.verify(notifier).发送欢迎信("user@example.com");
不要验证每个 getter 调用、调用顺序或私有实现步骤。重构内部代码不改变业务结果时,测试也不应大面积失败。
JUnit 的三个层次
JUnit Platform 负责启动测试引擎,Jupiter 提供 @Test 等编程模型,Vintage 用于临时运行旧版 JUnit 3/4 测试。
JUnit 6 用户指南说明 JUnit 6 运行时最低需要 Java 17。新项目可统一
使用 Jupiter;迁移期才保留 Vintage。
测试名称应描述场景和结果,例如 库存不足时拒绝创建订单。每个测试独立创建数据,不依赖执行顺序。时间、随机数和
外部 ID 通过接口注入,避免偶发失败。
测试金字塔不是配额
纯逻辑用快速单元测试;数据库映射、事务和序列化用集成测试;少量端到端测试验证关键用户旅程。Mock 无法证明 SQL、 网络协议或框架配置正确,这些边界要用真实组件或 Testcontainers 验证。
下一步
阅读Record、密封类与模式匹配,用类型表达封闭业务状态。