通过测试驱动错误处理代码
在本节中,我们将探讨存根对象的一个很好的用途,即它们在测试错误条件中的作用。
在我们创建代码时,我们需要确保它能够很好地处理错误条件。一些错误条件很容易测试。一个例子可能是用户输入验证器。为了测试它处理由无效数据引起的错误,我们只需编写一个测试,向其提供无效数据,然后编写一个断言以检查它是否成功报告数据无效。但是使用它的代码呢?
如果我们的 SUT 是响应其协作者之一引发的错误条件的代码,我们需要测试该错误响应。我们如何测试它取决于我们选择报告该错误的机制。我们可能使用简单的状态代码,在这种情况下,从存根返回该错误代码将非常有效。我们可能还选择使用Java异常来报告此错误。异常是有争议的。如果使用不当,它们会导致代码中的控制流非常不清晰。然而,我们需要知道如何测试它们,因为它们出现在几个 Java 库和内部编码风格中。幸运的是,编写异常处理代码的测试并不困难。
我们首先创建一个存根,使用本章中介绍的任何方法。然后我们需要安排在调用方法时存根抛出适当的异常。Mockito
有一个很好的功能可以做到这一点,所以让我们看一个使用异常的 Mockito
测试示例:
@Test
public void rejectsInvalidEmailRecipient() {
doThrow(new IllegalArgumentException())
.when(mailServer).sendEmail(any(), any(), any());
var notifications = new UserNotifications(mailServer);
assertThatExceptionOfType(NotificationFailureException.class)
.isThrownBy(() -> notifications.welcomeNewUser("not-an-email-address"));
}
java
在这个测试的开始,我们使用 Mockito
的 doThrow()
来配置我们的模拟对象。这配置了 Mockito
模拟对象 mailServer
,无论我们发送什么参数值,只要调用 sendEmail()
就会抛出 IllegalArgumentException
。这反映了一个设计决策,使 sendEmail()
抛出该异常作为报告电子邮件地址无效的机制。当我们的 SUT 调用 mailServer.sendEmail()
时,该方法将抛出 IllegalArgumentException
。我们可以执行处理此问题的代码。
对于这个例子,我们决定让 SUT 包装并重新抛出 IllegalArgumentException
。我们选择创建一个与用户通知责任相关的新异常。我们将其称为 NotificationFailureException
。测试的断言步骤然后使用 AssertJ
库的功能 assertThatExceptionOfType()
。这同时执行 Act
和 Assert
步骤。我们调用我们的 SUT welcomeNewUser()
方法,并断言它抛出我们的 NotificationFailureException
错误。
我们可以看到这足以触发 SUT 代码中的异常处理响应。这意味着我们可以编写我们的测试,然后驱动出所需的代码。我们编写的代码将包括一个用于 InvalidArgumentException
的 catch
处理程序。在这种情况下,所有新代码必须做的就是抛出一个 NotificationFailureException
错误。这是一个我们将创建的新类,它扩展了 RuntimeException
。我们这样做是为了通过发送通知来报告出了问题。
作为正常系统分层考虑的一部分,我们希望用更适合这层代码的更一般的异常替换原始异常。
本节已经探讨了 Mockito
和 AssertJ
库的功能,这些功能帮助我们使用 TDD 驱动出异常处理行为。在下一节中,让我们将其应用到我们的 Wordz 应用程序中的一个错误。