JUnit单元测试详解
什么是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));
}
}
测试最佳实践
- 每个测试方法只测试一个行为
- 测试方法命名要清晰有意义
- 使用@BeforeEach进行初始化
- 合理使用Mock隔离外部依赖
- 测试边界条件和异常情况
总结
JUnit是保证代码质量的重要工具。掌握JUnit的使用能帮助你编写可靠、可维护的测试代码。