不要过度依赖于 Mocks!

乔梁 | 2021-04-15

在为代码编写测试时,通过对代码的依赖关系进行 Mock ,让测试写起来似乎更容易。

然而,过度使用 Mocks 可能带来几个问题:

  • 让测试代码更难以理解。. 与直接使用代码(例如,将一些值传递给被测试的方法并检查返回结果)不同,你需要写一些额外的代码来告诉 mock 如何工作。而这些额外的代码会影响你要测试的内容的实际意图。如果你不熟悉生产代码的这些实现,通常就很难理解这些测试代码。

  • 测试用例更难为维护。 当你告诉一个 Mock 如何返回你期望的数据时,你是将代码的实现细节泄漏到了测试用例中。当产品代码中的实现细节发生变更时,您还需要修复相应的测试用例中的 Mock ,才能真实反映这些变更。测试通常应该对代码的实现知之甚少,而应该专注于验证生产代码的公共接口。

  • 测试用例无法保证代码能正常工作。当你告诉一个 Mock 如何返回你期望的数据时,你在测试中得到的唯一保证是:如果您的 Mock 的行为与实际实现完全相同,那么您的代码将正常工作。然而,这可能很难完全保证。而且,随着时间的推移和代码的变化,问题会变得更糟,因为实际上真实的代码行为很可能与你的 Mock 并不同步。

例如下面的例子。

public void testCreditCardIsCharged() {
  paymentProcessor = new PaymentProcessor(mockCreditCardServer);
  when(mockCreditCardServer.isServerAvailable()).thenReturn(true);
  when(mockCreditCardServer.beginTransaction()).thenReturn(mockTransactionManager);
  when(mockTransactionManager.getTransaction()).thenReturn(transaction);
  when(mockCreditCardServer.pay(transaction, creditCard, 500)).thenReturn(mockPayment);
  when(mockPayment.isOverMaxBalance()).thenReturn(false);
  paymentProcessor.processPayment(creditCard, Money.dollars(500));
  verify(mockCreditCardServer).pay(transaction, creditCard, 500);
}

然而,不使用 Mocks ,有时间会让测试用例更简单,更有效。

public void testCreditCardIsCharged() {
  paymentProcessor = new PaymentProcessor(creditCardServer);
  paymentProcessor.processPayment(creditCard, Money.dollars(500));
  assertEquals(500, creditCardServer.getMostRecentCharge(creditCard));
}

会有一些迹象显示你过度使用 Mock 了。例如,你模拟了多个类,或者其中的一个 Mock 指定了两个及以上方法的行为方式。如果你在阅读一个使用了 Mock 的测试用例,并且发现自己为了理解这个测试用例而不得不去理解被测的代码,那么你就有可能是过度使用 Mock 了。

有时,你无法在测试用例中使用真实的依赖项(例如,它太慢,或者需要通过网络进行会话)。但是,你也可能有比使用 Mock 技术更好的选择。例如,一个封闭的本地服务器(例如,在你自己的机器上专门为测试启动的一个信用卡服务器)或一个 Fake 实现(例如,内存中的信用卡服务器)。

关于更多关于密闭服务器的信息,请参见《 使用密闭服务(Hermetic Servers)进行测试》。


原文作者:Andrew Trenk

原文链接:Don’t Overuse Mocks

发表时间:May 28, 2013