3、断言机制:assertEquals、assertTrue、assertFalse、assertNull、assertThrows 等常用断言方法
断言,说白了就是「拍板定案」的那一下。
你写了一大堆测试代码,跑了一堆逻辑,最后怎么知道结果对不对?靠的就是断言。我个人习惯把断言看作是测试的「裁判员」——它不参与比赛,但吹哨的那一刻,决定了你的代码是赢是输。
3.1 assertEquals:最常用的「等不等」
这是你接触最多的断言方法。它的作用很简单:判断两个值是否相等。
@Test
void testAdd() {
Calculator calc = new Calculator();
int result = calc.add(2, 3);
assertEquals(5, result);
}
这里有个坑,我刚开始写测试时踩过好几次。assertEquals 的第一个参数是「期望值」,第二个才是「实际值」。顺序搞反了,测试照样能过,但报错信息会让人摸不着头脑。
我曾经接手过一个老项目,里面有个 User 类没重写 equals(),结果 assertEquals 怎么跑都是红的。排查了半天,最后发现是这个问题。嗯,从那以后我写新类第一件事就是生成 equals() 和 hashCode()。
3.2 assertTrue 与 assertFalse:真假判断
这两个方法用来判断布尔表达式。说白了就是「是」还是「不是」。
@Test
void testIsAdult() {
Person person = new Person(20);
assertTrue(person.isAdult());
Person child = new Person(15);
assertFalse(child.isAdult());
}
我个人习惯把 assertTrue 用在条件判断比较复杂的场景。比如验证一个集合是否为空,或者某个状态是否激活。
你想想看,如果测试失败了只给你一个「expected true but was false」,你还要去翻代码找原因。加个提示信息,省了多少事。
3.3 assertNull 与 assertNotNull:空与非空
这两个方法专门用来判断对象是否为 null。为什么需要单独的方法?因为 null 在 Java 里太特殊了,它既不是 true 也不是 false,更不等于任何值。
@Test
void testFindUser() {
UserService service = new UserService();
User user = service.findById(1L);
assertNotNull(user, "用户ID为1的记录应该存在");
User notExist = service.findById(999L);
assertNull(notExist, "不存在的用户应该返回null");
}
我记得有一次线上事故,就是因为一个方法在某些条件下返回了 null,但调用方没做判空处理,直接报了 NullPointerException。从那以后,我写测试时特别关注 null 的场景——不仅要测「有值」的情况,更要测「没值」的情况。
3.4 assertThrows:异常也是结果
这个方法是我最喜欢的。为什么?因为它让异常变成了可测试的「结果」,而不是测试的「意外」。
@Test
void testDivideByZero() {
Calculator calc = new Calculator();
assertThrows(ArithmeticException.class, () -> {
calc.divide(10, 0);
});
}
assertThrows 的第一个参数是期望的异常类型,第二个参数是一个 Lambda 表达式。如果这段代码抛出了指定类型的异常,测试通过;否则失败。
我曾经遇到过一个问题:一个方法在某些条件下会抛异常,但异常类型写错了。比如应该抛 IllegalArgumentException,结果抛了 RuntimeException。测试里用 assertThrows(IllegalArgumentException.class) 去测,死活不通过。后来发现是异常类型不匹配。嗯,这里要注意,异常类型必须精确匹配,子类不行。
@Test
void testDivideByZeroWithMessage() {
Calculator calc = new Calculator();
ArithmeticException exception = assertThrows(ArithmeticException.class, () -> {
calc.divide(10, 0);
});
assertEquals("/ by zero", exception.getMessage());
}
3.5 其他常用断言方法
除了上面几个,JUnit 5 还提供了一些实用的断言方法,我简单列一下:
| 方法 | 用途 | 示例 |
|---|---|---|
| assertSame | 判断两个引用是否指向同一个对象 | assertSame(expected, actual) |
| assertNotSame | 判断两个引用是否指向不同对象 | assertNotSame(expected, actual) |
| assertArrayEquals | 判断两个数组是否相等 | assertArrayEquals(expected, actual) |
| assertIterableEquals | 判断两个可迭代对象是否相等 | assertIterableEquals(expected, actual) |
| assertTimeout | 判断代码是否在指定时间内执行完成 | assertTimeout(Duration.ofSeconds(1), () -> { ... }) |
3.6 断言的最佳实践
写了这么多年测试,我总结了几条关于断言的经验:
- 一个测试方法只测一个逻辑点。 别在一个 @Test 方法里写七八个断言,那样测试挂了你都不知道是哪个条件出了问题。
- 断言要写清楚失败信息。 尤其是团队协作的项目,别人看到你的测试失败了,能根据提示信息快速定位问题。
- 别滥用 assertTrue。 能用 assertEquals 就用 assertEquals,因为 assertEquals 的报错信息更明确——它会告诉你期望值和实际值分别是什么。
- 异常测试不能少。 很多开发只测正常流程,忽略了异常流程。但线上出问题,往往就是异常流程没覆盖到。
我记得有一次,一个同事写的测试里全是 assertTrue,连判断两个值相等都用 assertTrue(a == b)。结果测试挂了,报错信息只有「expected true but was false」,他花了半小时才找到是哪个值不对。从那以后,我要求团队里必须用最精确的断言方法。
好了,断言这块就讲到这里。说白了,断言就是测试的「眼睛」——你看不到代码执行的过程,但断言能告诉你结果对不对。用好断言,你的测试才算真正有了灵魂。