单元测试

157 阅读2分钟

单元测试(Unit Testing)测试代码中的最小单元(比如一个函数或类,不包括接口),确保单个功能模块在孤立的情况下运行正常。

特点:不依赖外部系统(如数据库,API)

每写完一个类或接口,就应该立即为它编写单元测试,随着项目功能的扩展,如果修改了已有类,需要补充或更新相关的单元测试。

单元测试最优实践:

  1. 优先测试Service层和核心逻辑(如业务规则、工具类)
  2. 使用Mock框架(如Mockito)隔离外部依赖

假设现在需要对StudentServiceImpl这个类写单元测试,我们使用JUnit和Mockito

@Service
public class StudentServiceImpl implements StudentService{

    @Autowired
    private StudentRepository studentRepository;

    @Override
    public Student saveStudent(Student student) {
        return studentRepository.save(student);
    }

    @Override
    public List<Student> saveAllStudents(List<Student> studentList) {
        return studentRepository.saveAll(studentList);
    }

    @Override
    public void deleteById(Long id) {
        studentRepository.deleteById(id);
    }

    @Override
    public List<Student> getStudentByAgeGreaterThan(Integer age) {
        return studentRepository.findByAgeGreaterThan(age);
    }

    @Override
    public List<Student> searchStudentsByName(String namePart) {
        return studentRepository.findStudentsByNameContaining(namePart);
    }

    @Override
    public Long countStudentsInClass(Long classId) {
        return studentRepository.countStudentsByClassId(classId);
    }

    @Override
    public List<Student> getStudentsByClassId(Long classId) {
        return studentRepository.findStudentsByClassId(classId);
    }

}

步骤1:引入必要的依赖

以下都是非必需,选择添加的依赖

<!-- Junit 5 -->  
<dependency>  
<groupId>org.junit.jupiter</groupId>  
<artifactId>junit-jupiter</artifactId>  
</dependency>  
<!-- Mockito 核心 -->  
<dependency>  
<groupId>org.mockito</groupId>  
<artifactId>mockito-core</artifactId>  
</dependency>  
<!-- Mockito 对 static 支持 -->  
<dependency>  
<groupId>org.mockito</groupId>  
<artifactId>mockito-inline</artifactId>  
</dependency>  
<!-- Spring 对单元测试支持 -->  
<dependency>  
<groupId>org.springframework.boot</groupId>  
<artifactId>spring-boot-starter-test</artifactId>  
</dependency>
  • junit5:必须的组件,提供了最重要的注解和单元测试能力
  • mockito-core:必须的组件,提供了最重要的 Mock 的能力
  • mockito-inline:非必须组件,提供了对静态方法的 Mock 能力,如果不需要对静态方法进行 Mock 则可以不需要
  • spring-boot-starter-test:非必须组件,提供了对类的 private 变量的赋值能力,实际上反射也可以做到,但是通常为了方便,可以直接使用已经有的轮子

步骤2:编写测试类

public class StudentServiceImplTest {

    @InjectMocks   //创建并注入被测试类的依赖
    private StudentServiceImpl studentService;

    @Mock    //避免与实际数据库交互
    private StudentRepository studentRepository;

    @BeforeEach
    void setUp(){
        MockitoAnnotations.openMocks(this);  //初始化上面俩注解
    }

    @Test
    void testSaveStudent(){
        Student student = new Student(1L,"Tom",20,33L);
        when(studentRepository.save(student)).thenReturn(student);

        Student result = studentService.saveStudent(student);

        assertNotNull(result);
        assertEquals("Tom",result.getName());
        verify(studentRepository,times(1)).save(student);
    }

    @Test
    void testSaveAllStudents(){
        List<Student> students = Arrays.asList(
                new Student(1L,"Tom",18,23L),
                new Student(2L,"Jerry",19,33L)
        );
        when(studentRepository.saveAll(students)).thenReturn(students);

        List<Student> result = studentService.saveAllStudents(students);

        assertNotNull(result);
        assertEquals(2,result.size());
        verify(studentRepository,times(1)).saveAll(students);
    }

    @Test
    void testDeleteById(){
        studentService.deleteById(1L);

        verify(studentRepository,times(1)).deleteById(1L);

    }

    @Test
    void testGetStudentByAgeGreaterThan() {
        List<Student> students = Arrays.asList(
                new Student(1L, "Charlie", 22,10L),
                new Student(2L, "Dave", 25,23L)
        );
        when(studentRepository.findByAgeGreaterThan(20)).thenReturn(students);

        List<Student> result = studentService.getStudentByAgeGreaterThan(20);

        assertNotNull(result);
        assertEquals(2, result.size());
        verify(studentRepository, times(1)).findByAgeGreaterThan(20);
    }

    @Test
    void testSearchStudentsByName() {
        List<Student> students = Arrays.asList(
                new Student(1L, "Alice Smith", 18,23L),
                new Student(2L, "Alice Johnson", 19,12L)
        );
        when(studentRepository.findStudentsByNameContaining("Alice")).thenReturn(students);

        List<Student> result = studentService.searchStudentsByName("Alice");

        assertNotNull(result);
        assertEquals(2, result.size());
        verify(studentRepository, times(1)).findStudentsByNameContaining("Alice");
    }

    @Test
    void testCountStudentsInClass() {
        when(studentRepository.countStudentsByClassId(1L)).thenReturn(5L);

        Long count = studentService.countStudentsInClass(1L);

        assertNotNull(count);
        assertEquals(5L, count);
        verify(studentRepository, times(1)).countStudentsByClassId(1L);
    }

    @Test
    void testGetStudentsByClassId() {
        List<Student> students = Arrays.asList(
                new Student(1L, "Fiona", 20,10L),
                new Student(2L, "Frank", 21,23L)
        );
        when(studentRepository.findStudentsByClassId(1L)).thenReturn(students);

        List<Student> result = studentService.getStudentsByClassId(1L);

        assertNotNull(result);
        assertEquals(2, result.size());
        verify(studentRepository, times(1)).findStudentsByClassId(1L);
    }

}

参考资料:

  1. blog.mauve.icu/2022/04/22/…
  2. blog.csdn.net/weixin_4526…