Java 单元测试(JUnit)
单元测试是保证代码质量的重要手段。理解 JUnit 的使用是进行测试驱动开发的基础。本章将详细介绍 Java 中的单元测试。
JUnit 注解(@Test, @Before, @After)
基本注解
JUnit 5 常用注解:
import org.junit.jupiter.api.*;
public class JUnitExample {
// 测试方法
@Test
public void testMethod() {
// 测试代码
}
// 每个测试前执行
@BeforeEach
public void setUp() {
// 初始化
}
// 每个测试后执行
@AfterEach
public void tearDown() {
// 清理
}
// 所有测试前执行一次
@BeforeAll
public static void setUpAll() {
// 初始化(必须是静态方法)
}
// 所有测试后执行一次
@AfterAll
public static void tearDownAll() {
// 清理(必须是静态方法)
}
}
JUnit 4 常用注解:
import org.junit.*;
public class JUnit4Example {
@Before
public void setUp() {
// 每个测试前执行
}
@After
public void tearDown() {
// 每个测试后执行
}
@BeforeClass
public static void setUpClass() {
// 所有测试前执行一次
}
@AfterClass
public static void tearDownClass() {
// 所有测试后执行一次
}
@Test
public void testMethod() {
// 测试代码
}
}
断言方法(assertEquals, assertTrue)
常用断言
JUnit 5 断言:
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
public class AssertionExample {
@Test
public void testAssertions() {
// 相等断言
assertEquals(5, 2 + 3);
assertEquals("Hello", "Hello");
// 不相等断言
assertNotEquals(5, 2 + 2);
// 真值断言
assertTrue(5 > 3);
assertFalse(5 < 3);
// 空值断言
assertNull(null);
assertNotNull("Hello");
// 相同对象断言
String str1 = "Hello";
String str2 = str1;
assertSame(str1, str2);
assertNotSame(str1, new String("Hello"));
// 数组相等
assertArrayEquals(new int[]{1, 2, 3}, new int[]{1, 2, 3});
// 异常断言
assertThrows(ArithmeticException.class, () -> {
int result = 10 / 0;
});
}
}
JUnit 4 断言:
import org.junit.*;
import static org.junit.Assert.*;
public class JUnit4Assertion {
@Test
public void testAssertions() {
assertEquals(5, 2 + 3);
assertTrue(5 > 3);
assertFalse(5 < 3);
assertNull(null);
assertNotNull("Hello");
}
}
断言消息
@Test
public void testWithMessage() {
// 断言失败时显示消息
assertEquals(5, 2 + 2, "计算错误");
assertTrue(5 > 3, "5 应该大于 3");
}
测试套件与执行
测试类示例
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
public class CalculatorTest {
private Calculator calculator;
@BeforeEach
public void setUp() {
calculator = new Calculator();
}
@Test
public void testAdd() {
int result = calculator.add(3, 4);
assertEquals(7, result);
}
@Test
public void testSubtract() {
int result = calculator.subtract(5, 3);
assertEquals(2, result);
}
@Test
public void testMultiply() {
int result = calculator.multiply(3, 4);
assertEquals(12, result);
}
@Test
public void testDivide() {
double result = calculator.divide(10, 2);
assertEquals(5.0, result, 0.001);
}
@Test
public void testDivideByZero() {
assertThrows(ArithmeticException.class, () -> {
calculator.divide(10, 0);
});
}
}
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("除数不能为0");
}
return (double) a / b;
}
}
参数化测试
import org.junit.jupiter.params.*;
import org.junit.jupiter.params.provider.*;
import static org.junit.jupiter.api.Assertions.*;
@ParameterizedTest
@ValueSource(ints = {2, 4, 6, 8})
public void testEvenNumbers(int number) {
assertTrue(number % 2 == 0);
}
@ParameterizedTest
@CsvSource({
"2, 3, 5",
"5, 5, 10",
"10, 20, 30"
})
public void testAdd(int a, int b, int expected) {
Calculator calc = new Calculator();
assertEquals(expected, calc.add(a, b));
}
测试套件(JUnit 4)
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({
CalculatorTest.class,
StringUtilsTest.class,
MathUtilsTest.class
})
public class TestSuite {
// 测试套件类
}
测试执行
使用 Maven 执行测试:
# 运行所有测试
mvn test
# 运行特定测试类
mvn test -Dtest=CalculatorTest
# 运行特定测试方法
mvn test -Dtest=CalculatorTest#testAdd
使用 IDE 执行:
- 右键测试类或方法
- 选择 "Run Test"
实际示例
示例 1:字符串工具测试
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
public class StringUtilsTest {
@Test
public void testIsEmpty() {
assertTrue(StringUtils.isEmpty(null));
assertTrue(StringUtils.isEmpty(""));
assertFalse(StringUtils.isEmpty("Hello"));
}
@Test
public void testReverse() {
assertEquals("olleh", StringUtils.reverse("hello"));
assertEquals("", StringUtils.reverse(""));
assertNull(StringUtils.reverse(null));
}
}
class StringUtils {
public static boolean isEmpty(String str) {
return str == null || str.isEmpty();
}
public static String reverse(String str) {
if (str == null) {
return null;
}
return new StringBuilder(str).reverse().toString();
}
}
示例 2:用户服务测试
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
public class UserServiceTest {
private UserService userService;
@BeforeEach
public void setUp() {
userService = new UserService();
}
@Test
public void testCreateUser() {
User user = userService.createUser("张三", "zhangsan@example.com");
assertNotNull(user);
assertEquals("张三", user.getName());
assertEquals("zhangsan@example.com", user.getEmail());
}
@Test
public void testCreateUserWithInvalidEmail() {
assertThrows(IllegalArgumentException.class, () -> {
userService.createUser("张三", "invalid-email");
});
}
@Test
public void testFindUserById() {
User user = userService.createUser("张三", "zhangsan@example.com");
User found = userService.findUserById(user.getId());
assertNotNull(found);
assertEquals(user.getId(), found.getId());
}
@Test
public void testFindUserByIdNotFound() {
User found = userService.findUserById(999);
assertNull(found);
}
}
示例 3:Mock 测试
import org.junit.jupiter.api.*;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
public class OrderServiceTest {
@Mock
private UserDao userDao;
@Mock
private OrderDao orderDao;
private OrderService orderService;
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
orderService = new OrderService(userDao, orderDao);
}
@Test
public void testCreateOrder() {
User user = new User(1, "张三");
when(userDao.findById(1)).thenReturn(user);
Order order = orderService.createOrder(1, 100.0);
assertNotNull(order);
assertEquals(1, order.getUserId());
verify(orderDao).save(order);
}
}
单元测试的最佳实践
1. 测试命名规范
// ✅ 推荐:方法名_条件_期望结果
@Test
public void testAdd_WhenBothPositive_ReturnsSum() {
// ...
}
// 或使用描述性名称
@Test
public void shouldReturnSumWhenAddingTwoPositiveNumbers() {
// ...
}
2. 一个测试一个断言
// ✅ 推荐:一个测试一个断言
@Test
public void testAdd() {
assertEquals(5, calculator.add(2, 3));
}
// ⚠️ 可以:相关断言可以放在一起
@Test
public void testUserCreation() {
User user = userService.createUser("张三", "email@example.com");
assertNotNull(user);
assertEquals("张三", user.getName());
}
3. 使用 @BeforeEach 初始化
@BeforeEach
public void setUp() {
// 初始化测试数据
calculator = new Calculator();
userService = new UserService();
}
4. 测试异常
@Test
public void testDivideByZero() {
assertThrows(ArithmeticException.class, () -> {
calculator.divide(10, 0);
});
}
小结
Java 单元测试要点:
- JUnit 注解:@Test、@BeforeEach、@AfterEach、@BeforeAll、@AfterAll
- 断言方法:assertEquals、assertTrue、assertNull、assertThrows
- 测试套件:组织多个测试类
- 参数化测试:使用不同参数测试同一方法
- Mock 测试:模拟依赖对象
关键要点:
- 使用 JUnit 编写单元测试
- 使用断言验证结果
- 使用 @BeforeEach 初始化
- 测试正常情况和异常情况
- 保持测试独立和可重复
理解了单元测试,你就能保证代码质量。恭喜你完成了第十部分的学习!