跳到正文
Elaine Blog
返回

JUnit 与 Mockito:测试替身怎么选

Java 基础

测试替身不是为了“把覆盖率刷高”,而是让测试隔离慢、随机或不可控制的外部依赖。先判断要验证结果还是协作协议,再 决定是否需要 Mockito。

Table of contents

Open Table of contents

四种常见测试替身

能用简单 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、密封类与模式匹配,用类型表达封闭业务状态。


分享这篇文章:

上一篇
Maven、Gradle 与依赖冲突
下一篇
Record、密封类与模式匹配