← 返回首页

JUnit单元测试详解

📂 java ⏱ 3 min 537 words

什么是JUnit

JUnit是Java生态系统中最流行的单元测试框架,用于编写和运行可重复的自动化测试。

JUnit 5基础

第一个测试

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

public class CalculatorTest {
    @Test
    public void testAdd() {
        Calculator calc = new Calculator();
        assertEquals(5, calc.add(2, 3));
    }

    @Test
    public void testSubtract() {
        Calculator calc = new Calculator();
        assertEquals(1, calc.subtract(3, 2));
    }
}

被测试类

public 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 int divide(int a, int b) {
        if (b == 0) {
            throw new ArithmeticException("除数不能为零");
        }
        return a / b;
    }
}

断言方法

import org.junit.jupiter.api.Test;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.*;

public class AssertionTest {
    @Test
    public void testAssertions() {
        assertEquals(4, 2 + 2, "2 + 2 应该等于 4");
        assertNotEquals(5, 2 + 2);
        assertTrue(5 > 3);
        assertFalse(5 < 3);

        String str = "Hello";
        assertNotNull(str);
        assertNull(null);

        assertThrows(ArithmeticException.class, () -> {
            int result = 10 / 0;
        });

        assertArrayEquals(new int[]{1, 2, 3}, new int[]{1, 2, 3});

        assertTimeout(Duration.ofSeconds(1), () -> {
            Thread.sleep(100);
        });
    }
}

测试生命周期

import org.junit.jupiter.api.*;

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class LifecycleTest {
    @BeforeAll
    static void beforeAll() {
        System.out.println("所有测试前执行");
    }

    @AfterAll
    static void afterAll() {
        System.out.println("所有测试后执行");
    }

    @BeforeEach
    void beforeEach() {
        System.out.println("每个测试前执行");
    }

    @AfterEach
    void afterEach() {
        System.out.println("每个测试后执行");
    }

    @Test
    @Order(1)
    void testFirst() {
        System.out.println("第一个测试");
    }

    @Test
    @Order(2)
    void testSecond() {
        System.out.println("第二个测试");
    }
}

参数化测试

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

public class ParameterizedTest {
    @ParameterizedTest
    @ValueSource(ints = {1, 3, 5, 7, 9})
    void testOddNumbers(int number) {
        assertTrue(number % 2 != 0);
    }

    @ParameterizedTest
    @CsvSource({
        "1, 2, 3",
        "10, 20, 30",
        "100, 200, 300"
    })
    void testAddition(int a, int b, int expected) {
        Calculator calc = new Calculator();
        assertEquals(expected, calc.add(a, b));
    }
}

测试异常

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

public class ExceptionTest {
    @Test
    void testDivideByZero() {
        Calculator calc = new Calculator();
        assertThrows(ArithmeticException.class, () -> {
            calc.divide(10, 0);
        });
    }

    @Test
    void testExceptionMessage() {
        Calculator calc = new Calculator();
        ArithmeticException exception = assertThrows(
            ArithmeticException.class, () -> {
                calc.divide(10, 0);
            });
        assertEquals("除数不能为零", exception.getMessage());
    }
}

Mock测试

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

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

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    void testFindUser() {
        User mockUser = new User(1L, "张三");
        when(userRepository.findById(1L)).thenReturn(mockUser);

        User user = userService.findById(1L);

        assertNotNull(user);
        assertEquals("张三", user.getName());
        verify(userRepository, times(1)).findById(1L);
    }

    @Test
    void testSaveUser() {
        User user = new User("李四");
        when(userRepository.save(user)).thenReturn(new User(1L, "李四"));

        User saved = userService.save(user);

        assertNotNull(saved);
        assertEquals(1L, saved.getId());
        verify(userRepository).save(user);
    }
}

嵌套测试

import org.junit.jupiter.api.*;

@DisplayName("计算器测试套件")
public class CalculatorNestedTest {
    private Calculator calculator;

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

    @Nested
    @DisplayName("加法测试")
    class AdditionTests {
        @Test
        @DisplayName("正数相加")
        void testAddPositiveNumbers() {
            assertEquals(5, calculator.add(2, 3));
        }

        @Test
        @DisplayName("负数相加")
        void testAddNegativeNumbers() {
            assertEquals(-5, calculator.add(-2, -3));
        }
    }

    @Nested
    @DisplayName("除法测试")
    class DivisionTests {
        @Test
        @DisplayName("正常除法")
        void testDivide() {
            assertEquals(2, calculator.divide(10, 5));
        }

        @Test
        @DisplayName("除以零")
        void testDivideByZero() {
            assertThrows(ArithmeticException.class, () -> {
                calculator.divide(10, 0);
            });
        }
    }
}

测试覆盖率

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

public class CoverageTest {
    @Test
    void testEdgeCases() {
        Calculator calc = new Calculator();

        assertEquals(0, calc.add(0, 0));
        assertEquals(-1, calc.add(-1, 0));
        assertEquals(Integer.MAX_VALUE, calc.add(Integer.MAX_VALUE, 0));

        assertEquals(1, calc.subtract(1, 0));
        assertEquals(-1, calc.subtract(0, 1));

        assertEquals(0, calc.multiply(0, 100));
        assertEquals(1, calc.multiply(1, 1));
        assertEquals(-1, calc.multiply(-1, 1));
    }
}

测试最佳实践

  1. 每个测试方法只测试一个行为
  2. 测试方法命名要清晰有意义
  3. 使用@BeforeEach进行初始化
  4. 合理使用Mock隔离外部依赖
  5. 测试边界条件和异常情况

总结

JUnit是保证代码质量的重要工具。掌握JUnit的使用能帮助你编写可靠、可维护的测试代码。