正确捕获错误:2025年如何在JUnit中测试异常

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:“为什么我的测试在应该通过时却失败了?”

\n\n

您是否曾经编写过一个应该捕获异常的单元测试,但它却以红色错误告终?熟悉吗?问题 “How do you assert that a certain exception is thrown in JUnit tests?” 在Stack Overflow上获得了数千次浏览和数百条评论。这并非偶然:异常测试是即使经验丰富的开发人员有时也会犯错的主题之一。

\n\n

在2025年,当代码质量成为关键因素时(尤其是在微服务和AI管道中),正确验证异常的能力不仅仅是一种“良好礼仪”,而是一项基本技能。在本文中,我们将深入探讨 JUnit 5中所有当前断言异常的方法,展示潜在的陷阱,并提供最佳实践清单。我们开始吧!

\n\n

为什么测试异常很重要?

\n\n

想象一下:您正在编写一个方法 divide(int a, int b)。如果 b == 0,该方法应抛出 ArithmeticException。如果没有针对此行为的测试,您将面临风险:一个月后,另一位开发人员(或您自己)可能会意外删除检查,导致应用程序在生产环境中因不明错误而崩溃。

\n\n

异常测试是您的保障。它们记录了系统在边界情况下的预期行为。而JUnit 5为此提供了几种优雅的工具。

\n\n

方法1:assertThrows —— 现代测试之王

\n\n

从 JUnit 5(及其包 org.junit.jupiter.api)开始,assertThrows 方法已成为事实上的标准。为什么?因为它简洁、可读且功能强大。

\n\n
import static org.junit.jupiter.api.Assertions.assertThrows;\n\n@Test\nvoid whenDivideByZero_thenArithmeticException() {\n    assertThrows(ArithmeticException.class, \n        () -> calculator.divide(10, 0));\n}\n
\n\n

这里发生了什么?assertThrows 接受期望的异常类和应抛出该异常的lambda表达式。如果抛出异常,测试通过。如果没有,测试失败。简单明了。

\n\n

进阶用法:验证异常消息

\n\n

有时仅验证异常类型是不够的——您需要确保消息包含正确的文本。assertThrows 返回异常对象,您可以验证其属性:

\n\n
import static org.junit.jupiter.api.Assertions.*;\n\n@Test\nvoid whenInvalidEmail_thenExceptionWithMessage() {\n    IllegalArgumentException exception = assertThrows(\n        IllegalArgumentException.class,\n        () -> userService.register("invalid-email")\n    );\n    \n    assertTrue(exception.getMessage().contains("无效的电子邮件"));\n}\n
\n\n

这种方法在测试带有字段(例如错误代码)的自定义异常时特别有用。

\n\n

方法2:老派的 expected(JUnit 4 遗留方式)

\n\n

如果您正在使用 JUnit 4 或维护遗留代码,您可能遇到过 @Test 注解中的 expected 属性:

\n\n
@Test(expected = ArithmeticException.class)\npublic void whenDivideByZero_thenException() {\n    calculator.divide(10, 0);\n}\n
\n\n

缺点: 此方法不允许验证异常消息或其属性。此外,如果异常在您预期之前被抛出(例如,在初始化行中),测试可能会错误地通过。在2025年,我们建议在新代码中避免使用此方法。

\n\n

方法3:带标志的 Try-catch(适合控制狂)

\n\n

在JUnit 5出现之前,许多人使用带有 try-catch 和布尔标志的结构。看起来笨重,但提供了完全控制:

\n\n
@Test\nvoid whenDivideByZero_thenExceptionCaught() {\n    boolean thrown = false;\n    try {\n        calculator.divide(10, 0);\n    } catch (Arithmeti                

博客

书籍推荐