Java 单元测试简单扫盲

前言

仔细回想起来,上次认真编写单元测试已经是两年前的事了。那时候觉得写单元测试是种负担。

为了应付代码覆盖率要求,常常依赖工具自动生成测试用例,有时需要启动Spring容器,有时又不需要(当时还分不清单元测试和集成测试的区别)。

直到最近在开发过程中,当需要重构代码或自测功能时,我才真正体会到单元测试的价值。

概念

单元测试的本质是通过独立的测试用例来验证代码单元(方法/函数)的逻辑正确性。

在日常开发中,我们经常会在类里随手写一个 main 方法来快速验证逻辑,比如:

public static void main(String[] args) {
    int expectResult = 9;
    int result = 3 * 3;
    if (result == expectResult) {
        System.out.println("测试成功");
    } else {
        System.out.println("测试失败");
    }
}

测试分类

首先介绍一下,单元测试与集成测试的区别,刚开始学习的时候,没有分清它俩的区别,导致一直感觉单元测试是一个很复杂的东西。

集成测试

需要启动完整的 Spring 容器,容器内所有的 Bean 可以正常注入使用。通俗一点,相当于写了一个 TestController。

@SpringBootTest  // 启动完整Spring容器
class UserControllerIntegrationTest {
    @Autowired  // 真实注入Bean
    private UserController userController;

    @Test
    void testGetUser() {
        User user = userController.getUser(1L);
        assertNotNull(user);
    }
}

单元测试

不启动 Spring 容器,所有依赖需要手动模拟

@ExtendWith(MockitoExtension.class)  // 启用Mockito注解支持
class UserServiceUnitTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    void testGetUser() {
        when(userRepository.findById(1L))
               .thenReturn(new User(1L, "Mock用户"));

        User user = userService.getUser(1L);

        assertEquals("Mock用户", user.getName());
    }
}
维度 单元测试 集成测试
目标 验证单个代码单元(如方法、类)的功能正确性 验证多个模块、组件或外部系统协同工作的正确性
范围 隔离测试,不依赖外部系统(如数据库、网络、其他服务) 依赖外部系统或跨组件的交互(如数据库连接、API 调用)
覆盖范围 聚焦分支,验证代码片段逻辑正确性 聚焦模块间交互,验证系统整体行为

核心注解介绍

@Resource (Spring标准注解)

作用:用于依赖注入,会按照名称或类型从Spring容器中获取真实的bean
测试场景:集成测试中需要完全使用真实逻辑时

@MockBean (Spring Boot测试注解)

作用:向Spring应用上下文注入一个mock对象,替代原有的bean
测试场景:Spring Boot集成测试中需要mock某些bean时

@Spy (Mockito注解)

作用:创建部分mock对象,未mock的方法使用真实逻辑,mock的方法使用自定义逻辑
测试场景:需要保留对象大部分真实行为,仅修改少数方法逻辑的测试

@Mock (Mockito注解)

作用:创建完整mock对象,所有方法都需要mock,未mock的方法会返回默认值或空集合
测试场景:需要完全模拟依赖行为的单元测试

@InjectMocks (Mockito注解)

作用:自动将@Mock或@Spy创建的mock对象注入到被测试对象中
测试场景:需要自动装配依赖的单元测试

使用

依赖文件

添加依赖,具体版本需要根据项目版本自己替换

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <version>2.1.3.RELEASE</version>
</dependency>
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.13.1</version>
  <scope>test</scope>
</dependency>

单元测试

@Spy@Mock 的区别:

@Mock 所有方法都需要 mock,未 mock 的方法会返回默认值或空集合 ; @Spy 未 mock 的方法使用真实逻辑,mock 的方法使用自定义逻辑

注意:没有无参构造会初始化为空指针,需要手动实例化进行解决

@Spy
private UserService userService = new UserService(); // 显式提供实例

集成测试

@MockBean:向Spring上下文注入 Mock对象

@SpringBootTest
public class UserServiceTest {
    @Autowired
    private UserService userService; // 注入真实 UserService

    @MockBean
    private UserRepository userRepository; // 替换 Spring 容器中的 UserRepository

    @Test
    public void testGetUser() {
        User mockUser = new User(1, "Alice");
        when(userRepository.findById(1)).thenReturn(Optional.of(mockUser));

        User result = userService.getUser(1);

        assertEquals("Alice", result.getName());
        verify(userRepository).findById(1);
    }
}

总结

更多的内容还需要在探索后补充

posted @ 2025-04-19 12:11  帅气的涛啊  阅读(72)  评论(0)    收藏  举报