非受检异常必须显式测试,因其反映逻辑缺陷且编译器不强制处理;需用框架原生断言(如junit 5的assertthrows、pytest.raises)验证类型、消息及副作用,避免手动try-catch。

非受检异常(如 NullPointerException、IllegalArgumentException、ArrayIndexOutOfBoundsException)在单元测试中不强制捕获,但一旦发生,会直接中断执行、导致测试失败。它们反映的是程序逻辑缺陷,测试时不能忽略,而应主动触发、验证其行为是否符合预期。
为什么非受检异常必须显式测试
编译器不检查非受检异常,容易被开发者误认为“无需处理”。但实际中,它们暴露设计漏洞:参数校验缺失、空值未防护、边界未约束等。若不在单元测试中覆盖,这些错误只会在运行时爆发,且难以定位。
- 不抛出该抛的异常 → 说明防御逻辑缺失
- 抛出了但类型不对或消息模糊 → 说明错误提示不友好
- 异常发生后资源未清理或状态未回滚 → 说明恢复机制失效
主流框架中的异常断言写法
避免用 try-catch 手动包裹再断言,既冗长又易漏判。推荐使用框架原生支持的异常断言机制:
-
JUnit 5:用
assertThrows()捕获并校验类型与消息Exception e = assertThrows(IllegalArgumentException.class, () -> method("invalid"));assertEquals("值不能为负数", e.getMessage()); -
pytest:用
pytest.raises()上下文管理with pytest.raises(ValueError) as excinfo:calculate_discount(100, 1.5)assert "折扣率必须在0-1之间" in str(excinfo.value)
测试重点:不只是“抛没抛”,还要看“怎么抛”
仅验证异常类型是基础,还需关注业务语义层面的合理性:
-
输入边界触发:传
null、负数、超长字符串等,确认是否在第一时间抛出,而非深层调用后才崩溃 - 异常信息可读性:消息是否包含关键上下文(如字段名、非法值),便于调试和前端展示
-
副作用控制:抛出异常前是否已修改共享状态?数据库连接/文件句柄是否泄漏?需配合
@AfterEach或teardown检查资源状态
常见陷阱与规避方式
实践中容易踩坑,影响测试可靠性:
-
用
isinstance()判别 Python 自定义异常时,若类被 reload 或跨模块导入,可能返回False→ 改用excinfo.type is MyCustomError或直接比对__class__.__name__ -
测试异步方法抛出的非受检异常,未等待完成就断言 → 使用
Awaitility(Java)或asyncio.run()+pytest.raises()显式等待 -
Mock 对象抛出异常后,未重置状态,污染后续测试 → 在每个测试用例末尾调用
clearInvocations()(Mockito)或使用函数级 fixture(pytest)隔离











