java测试异常的核心是验证“该出错时确实出错且错得准确”,主流做法为junit 5的assertthrows()(推荐,简洁语义清晰)和try-catch+fail()(兼容性强、控制更细),二者适用场景不同。

Java 中测试代码是否抛出期望异常,核心是验证“该出错时确实出错,且错得准确”。主流做法有两种:JUnit 5 的 assertThrows()(推荐)和传统 try-catch + fail()(兼容性强、控制更细)。两者不是互斥,而是适用场景不同。
用 assertThrows() 快速验证异常类型
这是 JUnit 5 提供的最简洁、语义最清晰的方式。它直接声明“我期待这段代码抛出某类异常”,并返回异常实例供后续校验。
- 基本写法:assertThrows(异常类.class, () -> 被测方法调用)
- 如果被测代码没抛异常,或抛了别的异常,测试立即失败
- 支持链式断言:把返回的异常对象赋给变量,再检查 getMessage()、错误码等
示例:
@Test
void testNegativeFactorial() {
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
() -> Factorial.fact(-5));
assertEquals("Input cannot be negative", e.getMessage());
}
用 try-catch + fail() 精确控制校验逻辑
当需要在捕获异常后做多步判断(比如检查堆栈、多个字段、或执行清理动作),或者项目还在用 JUnit 4 时,这种写法更灵活可靠。
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
- 在 try 块中执行被测代码;若未抛异常,就调用 fail("应抛异常但没抛")
- 在 catch 块中拿到异常对象,可自由使用 assertEquals、assertTrue、assertNotNull 等任意断言
- 适合验证自定义异常的业务字段(如 errorCode、timestamp)
示例:
@Test
void testDivideByZero() {
Calculator calc = new Calculator();
try {
calc.divide(10, 0);
fail("应该抛出 ArithmeticException");
} catch (ArithmeticException e) {
assertTrue(e.getMessage().contains("除数不能为零"));
assertNotNull(e.getStackTrace());
}
}
避开常见误区
写异常测试容易踩坑,关键点在于“精准触发”和“避免误判”:
- 别依赖 @Test(expected = ...):JUnit 4 的这个注解已过时,它无法获取异常对象,也无法限定异常抛出位置(可能在 setup 或 teardown 阶段抛出,导致误通过)
- 确保异常路径真被执行:比如测试数据库异常,要用 Mockito 模拟 DataSource 抛 SQLException,而不是靠真实环境——否则测试不稳定
- 内部吞掉的异常没法直接测:如果方法 catch 了异常只打日志不重抛,assertThrows 就无效。此时应重构(比如改为抛出包装异常)或改测日志输出
补充:测试异常消息和业务字段
光看类型不够,业务系统常要求异常文案一致、错误码可识别。这时必须拿到异常实例:
- 用 assertThrows() 返回值,再对 e.getMessage() 断言
- 对自定义异常,可直接调用 e.getErrorCode()、e.getTimestamp() 等 getter 方法
- 避免链式调用空指针:如 e.getMessage().contains("xxx") 前,先确认 e 不为 null(assertThrows 已保证,但逻辑复杂时建议加 assertNotNull)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










