1. JUnit5 Assertions类概述
JUnit5的Assertions类是单元测试中最核心的工具类之一,它提供了一系列静态方法来验证测试结果是否符合预期。与JUnit4相比,JUnit5的断言机制进行了全面升级,不仅保留了传统的断言方法,还引入了许多新特性。
Assertions类的设计遵循了"失败即停止"的原则——当断言失败时,会立即抛出AssertionFailedError异常并终止当前测试方法的执行。这种设计能够快速定位问题,避免后续无效的断言执行。
提示:在JUnit5中,所有断言方法都支持通过Supplier延迟生成错误信息,这在构建复杂错误消息时可以提升性能。
2. 基础断言方法详解
2.1 布尔值断言
最基础的断言方法是对布尔条件进行验证:
@Test void testBooleanAssertions() { boolean condition = true; // 基本形式 assertTrue(condition); assertFalse(!condition); // 带自定义错误信息 assertTrue(condition, "条件应该为true"); assertFalse(!condition, () -> "延迟构建的错误信息"); // 使用BooleanSupplier assertTrue(() -> condition); assertFalse(() -> !condition); }实际项目中,我经常遇到开发者在复杂条件判断时直接使用assertTrue,这会导致错误信息不够明确。更好的做法是:
// 不推荐 assertTrue(user.getAge() > 18 && user.isVerified()); // 推荐 - 分开断言,错误更明确 assertTrue(user.getAge() > 18, "用户年龄不足"); assertTrue(user.isVerified(), "用户未验证");2.2 空值断言
空值检查是测试中常见的需求:
@Test void testNullAssertions() { Object obj = null; String notNull = "Hello"; assertNull(obj); assertNotNull(notNull); // 带自定义消息 assertNull(obj, "对象应该为null"); assertNotNull(notNull, () -> "延迟构建的错误信息"); }在测试DAO层时,我习惯使用assertNull验证删除操作,用assertNotNull验证插入操作:
@Test void testUserDeletion() { User user = userDao.getById(1); assertNotNull(user, "用户应该存在"); userDao.delete(1); user = userDao.getById(1); assertNull(user, "用户应该已被删除"); }3. 对象比较断言
3.1 相等性断言
assertEquals是最常用的断言之一:
@Test void testEquals() { // 基本类型 assertEquals(1, 1); assertEquals(3.14, 3.14, 0.001); // 带delta的浮点数比较 // 对象比较 String s1 = "hello"; String s2 = "hello"; assertEquals(s1, s2); // 带自定义消息 assertEquals(1, 1, "数字应该相等"); assertEquals(s1, s2, () -> "字符串应该相等"); }注意:对于浮点数比较,一定要指定delta值,否则可能因为精度问题导致测试失败。
3.2 不相等断言
assertNotEquals验证对象不相等:
@Test void testNotEquals() { assertNotEquals(1, 2); assertNotEquals("hello", "world"); }3.3 引用相等断言
assertSame和assertNotSame验证对象引用:
@Test void testReference() { Object obj1 = new Object(); Object obj2 = obj1; Object obj3 = new Object(); assertSame(obj1, obj2); assertNotSame(obj1, obj3); }4. 集合与数组断言
4.1 数组断言
assertArrayEquals验证数组内容:
@Test void testArray() { int[] expected = {1, 2, 3}; int[] actual = {1, 2, 3}; assertArrayEquals(expected, actual); // 浮点数组需要delta double[] d1 = {1.0, 2.0}; double[] d2 = {1.0, 2.0001}; assertArrayEquals(d1, d2, 0.001); }4.2 集合断言
assertIterableEquals验证集合内容:
@Test void testCollection() { List<Integer> list1 = Arrays.asList(1, 2, 3); List<Integer> list2 = new ArrayList<>(list1); assertIterableEquals(list1, list2); }4.3 多行文本匹配
assertLinesMatch是JUnit5新增的强大功能:
@Test void testMultilineText() { List<String> expected = Arrays.asList( "First line", ">> optional line >>", "Last line" ); List<String> actual = Arrays.asList( "First line", "Some debug output", "More debug info", "Last line" ); assertLinesMatch(expected, actual); }5. 高级断言功能
5.1 异常断言
assertThrows验证预期异常:
@Test void testException() { // 验证抛出特定异常 IllegalArgumentException ex = assertThrows( IllegalArgumentException.class, () -> { throw new IllegalArgumentException("test"); } ); // 可以进一步验证异常信息 assertEquals("test", ex.getMessage()); }5.2 超时断言
assertTimeout验证执行时间:
@Test void testTimeout() { // 允许方法执行完成 assertTimeout(Duration.ofMillis(100), () -> { Thread.sleep(50); }); // 超时立即失败 assertTimeoutPreemptively(Duration.ofMillis(100), () -> { Thread.sleep(150); // 会立即失败 }); }重要区别:assertTimeout会等待任务完成再检查超时,而assertTimeoutPreemptively会在超时后立即中断任务。
5.3 组合断言
assertAll执行多个断言,收集所有失败:
@Test void testMultipleAssertions() { User user = new User("John", 30); assertAll("user", () -> assertEquals("John", user.getName()), () -> assertEquals(30, user.getAge()), () -> assertTrue(user.isActive()) ); }6. 断言最佳实践
6.1 错误信息优化
好的错误信息能快速定位问题:
// 不好的做法 assertEquals(expected, actual); // 好的做法 assertEquals(expected, actual, String.format("用户年龄不匹配,预期:%d,实际:%d", expected, actual));6.2 自定义断言
对于领域对象,可以创建自定义断言:
public class UserAssertions { public static void assertAdult(User user) { assertNotNull(user, "用户不能为null"); assertTrue(user.getAge() >= 18, "用户" + user.getName() + "未成年"); } } // 使用 @Test void testCustomAssertion() { User user = new User("Alice", 20); UserAssertions.assertAdult(user); }6.3 性能考虑
对于性能敏感场景,使用Supplier延迟构建消息:
@Test void testPerformance() { // 错误信息只在断言失败时构建 assertTrue(complexCondition(), () -> buildExpensiveErrorMessage()); }7. 常见问题排查
7.1 浮点数比较问题
@Test void testFloatComparison() { // 错误做法 - 可能失败 // assertEquals(0.1 + 0.2, 0.3); // 正确做法 assertEquals(0.1 + 0.2, 0.3, 0.0001); }7.2 集合顺序问题
@Test void testCollectionOrder() { Set<String> set1 = new HashSet<>(Arrays.asList("a", "b")); Set<String> set2 = new HashSet<>(Arrays.asList("b", "a")); // 错误做法 - 集合顺序不保证 // assertIterableEquals(set1, set2); // 正确做法 - 转换为有序集合比较 assertIterableEquals( new ArrayList<>(set1).sorted(), new ArrayList<>(set2).sorted() ); }7.3 对象相等性实现
class User { String name; int age; // 必须正确实现equals方法 @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof User)) return false; User user = (User) o; return age == user.age && Objects.equals(name, user.name); } } @Test void testObjectEquality() { User u1 = new User("John", 30); User u2 = new User("John", 30); assertEquals(u1, u2); // 依赖正确的equals实现 }