Java单元测试案例如何编写

wen java案例 28

本文目录导读:

Java单元测试案例如何编写

  1. 单元测试基本结构
  2. 常用测试注解
  3. 断言方法详解
  4. Mockito模拟测试
  5. Spring Boot集成测试
  6. 测试数据驱动
  7. 最佳实践总结

我来详细介绍Java单元测试的编写方法和最佳实践。

单元测试基本结构

示例代码:待测试的类

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 IllegalArgumentException("除数不能为0");
        }
        return a / b;
    }
    public boolean isEven(int number) {
        return number % 2 == 0;
    }
}

对应的单元测试

import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
    private Calculator calculator;
    @BeforeEach
    void setUp() {
        calculator = new Calculator();
    }
    @AfterEach
    void tearDown() {
        calculator = null;
    }
    @Test
    @DisplayName("测试加法运算")
    void testAdd() {
        int result = calculator.add(5, 3);
        assertEquals(8, result, "5 + 3 应该等于 8");
    }
    @Test
    @DisplayName("测试除法运算")
    void testDivide() {
        int result = calculator.divide(10, 2);
        assertEquals(5, result);
    }
    @Test
    @DisplayName("测试除零异常")
    void testDivideByZero() {
        assertThrows(IllegalArgumentException.class, 
            () -> calculator.divide(10, 0));
    }
    @Test
    @DisplayName("测试偶数判断")
    void testIsEven() {
        assertTrue(calculator.isEven(4));
        assertFalse(calculator.isEven(3));
    }
}

常用测试注解

import org.junit.jupiter.api.*;
class TestLifecycleDemo {
    @BeforeAll
    static void initAll() {
        // 在所有测试方法执行前执行一次(必须为static)
        System.out.println("初始化测试环境");
    }
    @AfterAll
    static void cleanAll() {
        // 在所有测试方法执行后执行一次(必须为static)
        System.out.println("清理测试环境");
    }
    @BeforeEach
    void init() {
        // 每个测试方法执行前执行
        System.out.println("准备测试数据");
    }
    @AfterEach
    void clean() {
        // 每个测试方法执行后执行
        System.out.println("清理测试数据");
    }
    @Test
    @DisplayName("示例测试方法")
    void testExample() {
        System.out.println("执行测试");
    }
    @Test
    @Disabled("暂时跳过此测试")
    void skippedTest() {
        // 被跳过的测试
    }
}

断言方法详解

class AssertionDemo {
    @Test
    void testAssertions() {
        // 基本断言
        assertEquals(5, 5, "值应该相等");
        assertNotEquals(5, 10, "值不应该相等");
        // 布尔断言
        assertTrue(10 > 5, "条件应该为true");
        assertFalse(5 > 10, "条件应该为false");
        // 对象断言
        String str1 = new String("hello");
        String str2 = new String("hello");
        assertEquals(str1, str2, "对象内容应该相等");
        assertNotSame(str1, str2, "对象引用应该不同");
        // 空值断言
        String nullStr = null;
        assertNull(nullStr, "对象应该为null");
        assertNotNull("hello", "对象不应该为null");
        // 数组断言
        int[] expected = {1, 2, 3};
        int[] actual = {1, 2, 3};
        assertArrayEquals(expected, actual, "数组应该相等");
        // 异常断言
        assertThrows(IllegalArgumentException.class, () -> {
            throw new IllegalArgumentException("测试异常");
        });
        // 多断言组合
        assertAll("多个断言",
            () -> assertEquals(1, 1),
            () -> assertTrue(true),
            () -> assertNotNull("test")
        );
    }
}

Mockito模拟测试

// 添加Maven依赖
/*
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>4.0.0</version>
    <scope>test</scope>
</dependency>
*/
import org.mockito.*;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
// 待测试的服务类
class UserService {
    private UserRepository userRepository;
    private EmailService emailService;
    public UserService(UserRepository userRepository, EmailService emailService) {
        this.userRepository = userRepository;
        this.emailService = emailService;
    }
    public User createUser(String name, String email) {
        User user = new User(name, email);
        User saved = userRepository.save(user);
        emailService.sendWelcomeEmail(email);
        return saved;
    }
}
// Mockito测试
class UserServiceTest {
    @Mock
    private UserRepository userRepository;
    @Mock
    private EmailService emailService;
    @InjectMocks
    private UserService userService;
    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
    }
    @Test
    void testCreateUser() {
        // 准备Mock数据
        User testUser = new User("张三", "zhangsan@example.com");
        when(userRepository.save(any(User.class))).thenReturn(testUser);
        // 执行测试
        User result = userService.createUser("张三", "zhangsan@example.com");
        // 验证结果
        assertEquals("张三", result.getName());
        assertEquals("zhangsan@example.com", result.getEmail());
        // 验证方法调用
        verify(userRepository, times(1)).save(any(User.class));
        verify(emailService, times(1)).sendWelcomeEmail("zhangsan@example.com");
    }
    @Test
    void testCreateUserException() {
        // 模拟异常
        when(userRepository.save(any(User.class)))
            .thenThrow(new RuntimeException("数据库异常"));
        // 验证异常
        assertThrows(RuntimeException.class, 
            () -> userService.createUser("张三", "zhangsan@example.com"));
        // 验证邮件不被发送
        verify(emailService, never()).sendWelcomeEmail(anyString());
    }
}

Spring Boot集成测试

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.beans.factory.annotation.Autowired;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
    @Autowired
    private MockMvc mockMvc;
    @Test
    void testGetUser() throws Exception {
        mockMvc.perform(get("/api/users/1")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("张三"))
                .andExpect(jsonPath("$.email").value("zhangsan@example.com"));
    }
    @Test
    void testCreateUser() throws Exception {
        String userJson = "{\"name\":\"李四\",\"email\":\"lisi@example.com\"}";
        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(userJson))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.name").value("李四"));
    }
}

测试数据驱动

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.*;
class ParameterizedTestDemo {
    @ParameterizedTest
    @ValueSource(ints = {2, 4, 6, 8, 10})
    void testEvenNumbers(int number) {
        Calculator calculator = new Calculator();
        assertTrue(calculator.isEven(number));
    }
    @ParameterizedTest
    @CsvSource({
        "1, 2, 3",
        "10, 20, 30",
        "100, 200, 300"
    })
    void testAddWithMultipleInputs(int a, int b, int expected) {
        Calculator calculator = new Calculator();
        assertEquals(expected, calculator.add(a, b));
    }
    @ParameterizedTest
    @MethodSource("provideTestData")
    void testWithMethodSource(String input, int expected) {
        assertEquals(expected, input.length());
    }
    static Stream<Arguments> provideTestData() {
        return Stream.of(
            Arguments.of("hello", 5),
            Arguments.of("world", 5),
            Arguments.of("test", 4)
        );
    }
}

最佳实践总结

测试命名规范

// 方法命名:方法名_场景_期望结果
@Test
void calculateTotalPrice_whenNoDiscount_shouldReturnOriginalPrice() {
    // ...
}
@Test
void calculateTotalPrice_whenVIPCustomer_shouldApplyDiscount() {
    // ...
}

测试结构(AAA模式)

@Test
void testSomething() {
    // Arrange(准备)
    Calculator calculator = new Calculator();
    int expected = 10;
    // Act(执行)
    int actual = calculator.add(5, 5);
    // Assert(验证)
    assertEquals(expected, actual);
}

注意事项

  1. 单一职责:每个测试方法只测试一个功能点
  2. 独立性:测试之间不应相互依赖
  3. 可重复性:同一测试多次执行结果应一致
  4. 明确性:测试名称应清晰表达测试意图
  5. 隔离性:使用Mock隔离外部依赖
  6. 覆盖率:关注核心逻辑和边界条件的覆盖

这就是Java单元测试的基本写法,实际开发中,建议结合项目的具体需求和框架选择合适的测试策略。

抱歉,评论功能暂时关闭!