← 返回首页

Java JUnit测试详解:单元测试与集成测试

📂 java ⏱ 3 min 521 words

Java JUnit测试详解:单元测试与集成测试

概述

JUnit是Java中最流行的单元测试框架。本教程介绍JUnit的使用方法和最佳实践。

1. JUnit基础

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

public class CalculatorTest {
    
    private Calculator calculator;
    
    @BeforeEach
    void setUp() {
        calculator = new Calculator();
    }
    
    @AfterEach
    void tearDown() {
        calculator = null;
    }
    
    @Test
    void testAdd() {
        assertEquals(5, calculator.add(2, 3));
    }
    
    @Test
    void testSubtract() {
        assertEquals(1, calculator.subtract(3, 2));
    }
    
    @Test
    void testMultiply() {
        assertEquals(6, calculator.multiply(2, 3));
    }
    
    @Test
    void testDivide() {
        assertEquals(2.0, calculator.divide(4, 2), 0.001);
    }
    
    @Test
    void testDivideByZero() {
        assertThrows(ArithmeticException.class, () -> {
            calculator.divide(1, 0);
        });
    }
    
    @Test
    void testWithTimeout() {
        assertTimeout(Duration.ofMillis(100), () -> {
            calculator.add(1, 2);
        });
    }
}

class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
    
    public int subtract(int a, int b) {
        return a - b;
    }
    
    public int multiply(int a, int b) {
        return a * b;
    }
    
    public double divide(int a, int b) {
        if (b == 0) {
            throw new ArithmeticException("除数不能为零");
        }
        return (double) a / b;
    }
}

2. 参数化测试

import org.junit.jupiter.params.*;
import org.junit.jupiter.params.provider.*;
import static org.junit.jupiter.api.Assertions.*;

public class ParameterizedTest {
    
    @ParameterizedTest
    @ValueSource(ints = {1, 2, 3, 4, 5})
    void testIsPositive(int number) {
        assertTrue(number > 0);
    }
    
    @ParameterizedTest
    @CsvSource({
        "1, 2, 3",
        "4, 5, 9",
        "10, 20, 30"
    })
    void testAdd(int a, int b, int expected) {
        assertEquals(expected, a + b);
    }
    
    @ParameterizedTest
    @CsvFileSource(resources = "/test-data.csv", numLinesToSkip = 1)
    void testWithCsvFile(String input, String expected) {
        assertEquals(expected, input.toUpperCase());
    }
    
    @ParameterizedTest
    @MethodSource("provideStringsForIsBlank")
    void testIsBlank(String input, boolean expected) {
        assertEquals(expected, input.isBlank());
    }
    
    static Stream<Arguments> provideStringsForIsBlank() {
        return Stream.of(
            Arguments.of("", true),
            Arguments.of("  ", true),
            Arguments.of("not blank", false)
        );
    }
}

3. 集成测试

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

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class UserServiceIntegrationTest {
    
    private UserService userService;
    
    @BeforeAll
    static void setUp() {
        // 初始化数据库连接等
    }
    
    @AfterAll
    static void tearDown() {
        // 清理资源
    }
    
    @BeforeEach
    void init() {
        userService = new UserService();
    }
    
    @Test
    @Order(1)
    void testCreateUser() {
        User user = userService.createUser("Alice", 25);
        assertNotNull(user.getId());
        assertEquals("Alice", user.getName());
    }
    
    @Test
    @Order(2)
    void testFindUser() {
        User user = userService.createUser("Bob", 30);
        User found = userService.findById(user.getId());
        assertEquals("Bob", found.getName());
    }
    
    @Test
    @Order(3)
    void testDeleteUser() {
        User user = userService.createUser("Charlie", 35);
        userService.delete(user.getId());
        assertNull(userService.findById(user.getId()));
    }
}

class UserService {
    public User createUser(String name, int age) {
        return new User(1L, name, age);
    }
    
    public User findById(Long id) {
        return new User(id, "Test", 25);
    }
    
    public void delete(Long id) {
        // 删除用户
    }
}

class User {
    private Long id;
    private String name;
    private int age;
    
    public User(Long id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    
    public Long getId() { return id; }
    public String getName() { return name; }
    public int getAge() { return age; }
}

4. Mock测试

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

@ExtendWith(MockitoExtension.class)
public class MockTest {
    
    @Mock
    private UserRepository userRepository;
    
    @InjectMocks
    private UserService userService;
    
    @Test
    void testCreateUser() {
        User user = new User(1L, "Alice", 25);
        when(userRepository.save(any(User.class))).thenReturn(user);
        
        User created = userService.createUser("Alice", 25);
        
        assertNotNull(created);
        assertEquals("Alice", created.getName());
        verify(userRepository, times(1)).save(any(User.class));
    }
    
    @Test
    void testFindUser() {
        User user = new User(1L, "Bob", 30);
        when(userRepository.findById(1L)).thenReturn(Optional.of(user));
        
        User found = userService.findById(1L);
        
        assertEquals("Bob", found.getName());
        verify(userRepository, times(1)).findById(1L);
    }
}

interface UserRepository {
    User save(User user);
    Optional<User> findById(Long id);
}

5. 最佳实践

  1. 遵循AAA模式:Arrange-Act-Assert
  2. 每个测试只测一个功能
  3. 使用有意义的测试方法名
  4. 测试边界条件
  5. 使用Mock隔离依赖

总结

JUnit是Java单元测试的标准框架。掌握JUnit的使用方法,可以编写高质量的测试代码。在实际编程中,要编写完整的测试用例,确保代码质量。