Java单元测试实战指南:从基础到Mockito高级技巧

1次阅读
没有评论

共计 2088 个字符,预计需要花费 6 分钟才能阅读完成。

image.webp

线上事故引发的思考

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

Java 单元测试实战指南:从基础到 Mockito 高级技巧

JUnit 5 的新世界

相比 JUnit 4,JUnit 5 带来的不仅是 API 变化,更是测试思维的升级:

  1. 架构变革 :将 Runner 机制改为扩展模型 (Extension API),支持更多定制可能
  2. 嵌套测试 :通过 @Nested 实现测试用例的层次化组织,符合 BDD 风格
  3. 动态测试 :用 @TestFactory 生成运行时决定的测试用例,适合数据驱动测试
  4. 参数化测试 :@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 原则实践

  1. 使用 @BeforeEach 初始化通用测试数据
  2. 对重复的验证逻辑封装为自定义断言
  3. 提取测试数据生成器到独立工厂类

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>

思考题:领域事件测试

领域驱动设计中的领域事件测试需要考虑:

  1. 事件是否被正确发布(次数、内容)
  2. 事件处理器的幂等性保证
  3. 事件时序的验证策略

参考答案方向:

  • 使用 Mockito 的 InOrder 验证事件发布顺序
  • 通过自定义 EventCollector 收集所有发布的事件
  • 对事件处理器进行边界条件测试(空事件、重复事件等)

单元测试不是银弹,但好的测试习惯就像代码的安全带。当你在深夜部署代码时,那些绿色的测试结果会给足你信心。记住:测试代码的质量与业务代码同等重要,因为它们共同构成了系统的可维护性基石。

正文完
 0
评论(没有评论)