1. 引言
本文将深入探讨如何使用JUnit库测试代码是否抛出异常,以及如何验证代码未抛出任何异常。我们将同时覆盖JUnit 4和JUnit 5两个版本的核心方法。
2. JUnit 5异常处理
2.1. 断言异常抛出
JUnit 5 Jupiter断言API引入了assertThrows
方法专门用于异常断言。该方法接收两个参数:
- 预期的异常类型
- 一个
Executable
函数式接口(通过Lambda表达式包裹待测代码)
@Test
void whenExceptionThrown_thenAssertionSucceeds() {
Exception exception = assertThrows(NumberFormatException.class, () -> {
Integer.parseInt("1a");
});
String expectedMessage = "For input string";
String actualMessage = exception.getMessage();
assertTrue(actualMessage.contains(expectedMessage));
}
核心要点:
✅ 当预期异常抛出时,assertThrows
会返回异常实例,便于进一步验证异常信息
✅ 该断言满足多态匹配:当抛出NumberFormatException
或其子类时断言均会通过
⚠️ 若将预期类型设为Exception
,则任何异常都会使断言通过(因为Exception
是所有异常的父类)
多态匹配示例:
@Test
void whenDerivedExceptionThrown_thenAssertionSucceeds() {
Exception exception = assertThrows(RuntimeException.class, () -> {
Integer.parseInt("1a"); // 实际抛出NumberFormatException
});
String expectedMessage = "For input string";
String actualMessage = exception.getMessage();
assertTrue(actualMessage.contains(expectedMessage));
}
2.2. 断言无异常抛出
使用assertDoesNotThrow()
验证代码块执行时不抛出异常:
@Test
void givenABlock_whenExecutes_thenEnsureNoExceptionThrown() {
assertDoesNotThrow(() -> {
Integer.parseInt("100"); // 正常执行
});
}
执行逻辑:
- 代码块无异常 → 测试通过
- 代码块抛出异常 → 测试失败
2.3. 断言特定类型异常未抛出
JUnit未内置此功能,需自定义实现。首先定义函数式接口:
@FunctionalInterface
public interface Executable {
void execute() throws Exception;
}
然后实现自定义断言方法:
private <T extends Exception> void assertSpecificExceptionIsNotThrown(Class<T> exceptionClass, Executable executable) {
try {
executable.execute();
} catch (Exception e) {
if (exceptionClass.isInstance(e)) {
fail(e.getClass().getSimpleName() + " was thrown");
} else {
// 其他异常类型被忽略,测试通过!
// 记录日志便于调试
LOG.info("Caught exception: " + e.getClass().getName() + ", but ignoring since it is not an instance of " + exceptionClass.getName());
}
}
}
使用示例:
@Test
void givenASpecificExceptionType_whenBlockExecutes_thenEnsureThatExceptionIsNotThrown() {
assertSpecificExceptionIsNotThrown(IllegalArgumentException.class, () -> {
int i = 100 / 0; // 实际抛出ArithmeticException
});
}
行为说明:
- 抛出
IllegalArgumentException
或其子类 → 测试失败 - 抛出其他异常或无异常 → 测试通过
3. JUnit 4异常处理
3.1. 断言异常抛出
基础方案: 使用@Test
注解的expected
属性
@Test(expected = NullPointerException.class)
public void whenExceptionThrown_thenExpectationSatisfied() {
String test = null;
test.length(); // 触发NullPointerException
}
进阶方案: 使用ExpectedException
规则验证异常属性
@Rule
public ExpectedException exceptionRule = ExpectedException.none();
@Test
public void whenExceptionThrown_thenRuleIsApplied() {
exceptionRule.expect(NumberFormatException.class);
exceptionRule.expectMessage("For input string");
Integer.parseInt("1a"); // 触发异常
}
对比说明:
| 方案 | 优点 | 局限性 |
|------|------|--------|
| expected
属性 | 简单直接 | 无法验证异常信息 |
| ExpectedException
规则 | 可验证异常类型/消息/原因 | 需要额外声明Rule |
3.2. 断言无异常抛出
JUnit 4无内置方法,需手动实现:
private void assertNoExceptionIsThrown(Executable executable) {
try {
executable.execute();
} catch (Exception e) {
fail(e.getClass().getSimpleName() + " was thrown");
}
}
使用示例:
@Test
public void givenABlock_whenExecuted_thenEnsureThatNoExceptionAreThrown() {
assertNoExceptionIsThrown(() -> {
int d = 100 / 10; // 正常执行
});
}
执行逻辑:
- 代码块无异常 → 测试通过
- 代码块抛出任何异常 → 测试失败
4. 总结
本文系统梳理了JUnit 4和5中的异常断言方案:
JUnit 5优势:
- ✅
assertThrows
支持Lambda表达式,定位更精准 - ✅ 内置
assertDoesNotThrow
方法 - ✅ 异常返回值便于深度验证
JUnit 4特点:
- ✅
@Test(expected)
简单易用 - ✅
ExpectedException
规则提供灵活验证 - ❌ 无内置"无异常"断言方法
最佳实践建议:
- 优先使用JUnit 5的现代API
- 需要验证异常细节时,避免使用
@Test(expected)
- 自定义断言方法时注意异常类型的多态特性
完整示例代码请查阅:GitHub仓库