JUnit 简介

JUnit 是 Java 语言中最著名的单元测试框架之一,由 Kent Beck 和 Erich Gamma 在 1997 年共同创建。作为 xUnit 家族(如 Python 的 PyUnit、C++ 的 CppUnit)的一员,JUnit 通过提供一套简洁的 API 和注解机制,帮助开发者编写可复用、自动化的测试用例。

JUnit 的核心功能

  • 断言机制:通过 assertEqualsassertTrue 等方法验证代码逻辑是否符合预期。
  • 生命周期管理:通过注解(如 @BeforeEach@AfterEach)控制测试方法的执行顺序。
  • 参数化测试:支持多组输入数据的测试,提升测试覆盖率。
  • 扩展性:允许通过自定义扩展点(如条件测试、动态测试)增强框架功能。

随着 JUnit 5 的发布,框架进一步引入了模块化设计(Platform、Jupiter、Vintage)、Lambda 表达式支持以及更灵活的扩展模型,成为现代 Java 开发中不可或缺的工具。

JUnit 的优点

  1. 自动化测试与快速反馈
    JUnit 将测试执行自动化,避免了手动测试的人为错误和时间消耗。开发者可以在代码修改后立即运行测试,快速定位问题,减少回归错误的修复成本。

  2. 提升代码质量与可维护性
    通过针对方法或类的单元测试,JUnit 强制开发者在编码前明确需求逻辑(如极限编程中的“先写测试”原则),从而降低代码冗余度,提高代码的健壮性和可维护性。

  3. 兼容性与生态整合
    JUnit 5 不仅兼容旧版 JUnit 4 的测试代码(通过 Vintage 模块),还无缝集成 Spring Boot、Mockito 等主流框架,支持微服务架构下的集成测试和端到端测试。


JUnit 5 在 Spring Boot 3 中的应用

Spring Boot 3 对 JUnit 5 提供了深度集成,开发者可以利用其强大的测试功能构建高质量的 Java 应用。以下是结合 Spring Boot 3 使用 JUnit 5 的关键实践:

1. 环境准备

Spring Boot 3 默认包含 spring-boot-starter-test 依赖,其中已集成 JUnit 5、Mockito 和 AssertJ 等工具。确保 pom.xml 中包含以下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>3.4.1</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>3.4.1</version>
    <scope>test</scope>
</dependency>

2. 准备被测试的类

在进行测试之前,需要准备被测试的类。例如,下面是一个简单的 Calculator 类:

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }

    public int divide(int a, int b) {
        if (b == 0) {
            throw new ArithmeticException("除数不能为零");
        }
        return a / b;
    }
}
  • 用户实体类 User
import org.springframework.stereotype.Component;

@Component
public class User {
    private Long id;
    private String name;

    public User() {}

    public User(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • 用户接口 UserRepository
import org.springframework.stereotype.Repository;
import java.util.Optional;

@Repository
public interface UserRepository {
    Optional<User> findById(Long id);
}
  • 用户服务类 UserService
import org.springframework.stereotype.Service;
import java.util.Optional;

@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public Optional<User> getUserById(Long id) {
        return userRepository.findById(id);
    }
}

3. 基础单元测试

使用 JUnit 5 编写测试用例,通过 @Test 注解标记测试方法,并结合断言验证逻辑:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {

    private Calculator calculator;

    @BeforeEach
    void setUp() {
        calculator = new Calculator();
    }

    @Test
    void testAdd() {
        assertEquals(4, calculator.add(2, 2), "2 + 2 应该等于 4");
    }

    @Test
    void testDivideByZero() {
        assertThrows(ArithmeticException.class, () -> calculator.divide(1, 0));
    }
}

4. 参数化测试

通过 @ParameterizedTest@CsvSource 等注解运行多组测试数据:

@ParameterizedTest
@ValueSource(strings = {"apple", "banana", "cherry"})
void testFruitLength(String fruit) {
    assertTrue(fruit.length() > 3);
}

@ParameterizedTest
@CsvSource({
    "1, 2, 3",
    "10, 20, 30"
})
void testAdd(int a, int b, int expected) {
    assertEquals(expected, calculator.add(a, b));
}

支持数据源类型:

  • @ValueSource:单类型值集合
  • @CsvSource:CSV 格式参数
  • @MethodSource:通过方法提供参数
  • @EnumSource:枚举类型参数

5. 单元测试

使用 @Mock + @InjectMocks 进行单元测试,不依赖 Spring 上下文,速度快。

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

class UserServiceUnitTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    void testGetUserById_UserExists() {
        // 模拟数据
        User expectedUser = new User(1L, "Alice");
        when(userRepository.findById(1L)).thenReturn(Optional.of(expectedUser));

        // 调用服务方法
        Optional<User> result = userService.getUserById(1L);

        // 验证结果
        assertTrue(result.isPresent());
        assertEquals(expectedUser, result.get());
    }

    @Test
    void testGetUserById_UserNotFound() {
        // 模拟数据
        when(userRepository.findById(1L)).thenReturn(Optional.empty());

        // 调用服务方法
        Optional<User> result = userService.getUserById(1L);

        // 验证结果
        assertFalse(result.isPresent());
    }
}

6. 集成测试

使用 @SpringBootTest 加载完整的 Spring 上下文,并结合 @MockBean 模拟依赖,进行集成测试。
由于 @MockBean 在 Spring Boot 3.4.0+ 中被弃用,推荐使用 Mockito 手动管理 Mock 对象,而不是依赖 Spring 上下文。

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

@SpringBootTest(classes = Application.class)
class UserServiceIntegrationTest {

    @Autowired
    private UserService userService;
    
    @MockBean
    private UserRepository userRepository;

    @BeforeEach
    void setUp() {
        // 如果未来完全废弃 @MockBean,可手动创建 Mock
        // userRepository = mock(UserRepository.class);
        // userService = new UserService(userRepository);
    }

    @Test
    void testGetUserById_UserExists() {
        // 模拟数据
        User expectedUser = new User(1L, "Alice");
        when(userRepository.findById(1L)).thenReturn(Optional.of(expectedUser));

        // 调用服务方法
        Optional<User> result = userService.getUserById(1L);

        // 验证结果
        assertTrue(result.isPresent());
        assertEquals(expectedUser, result.get());
    }

    @Test
    void testGetUserById_UserNotFound() {
        // 模拟数据
        when(userRepository.findById(1L)).thenReturn(Optional.empty());

        // 调用服务方法
        Optional<User> result = userService.getUserById(1L);

        // 验证结果
        assertFalse(result.isPresent());
    }
}

7. 并行测试执行

junit-platform.properties 中启用并行测试,提升测试效率:

junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐