不要过度依赖于 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 技术更好的选择。比如使用封闭服务器(例如,在你自己的机器上专门为测试启动的一个信用卡服务器)或者一个 伪实现( Fake implementation,比如建立在内存中的信用卡服务器)。

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


发表时间:May 28, 2013

原文作者:Andrew Trenk

原文链接Don’t Overuse Mocks