4

不要模拟( Mock )不属于你负责的组件!

 2 years ago
source link: https://www.continuousdelivery20.com/blog/tott-dont-mock-types-you/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
不要模拟( Mock )不属于你负责的组件!

乔梁 | 2021-04-18

本文将讨论如何以及在什么地方应该使用 Mock 技术来模拟第三方库或外部组件。

虽然对外部组件的模拟( Mocks )让你能够验证系统的边界,而不必真正使用外部系统。但是,它也创建了一个不自然且有可能偏离了真实代码的机会,引入测试风险。

下面的测试代码就使用了 Mock 技术来模拟第三方库。

这么做,可能会引发什么样的问题呢?

// Mock a salary payment library
@Mock SalaryProcessor mockSalaryProcessor;
@Mock TransactionStrategy mockTransactionStrategy;
...
when(mockSalaryProcessor.addStrategy()).thenReturn(mockTransactionStrategy);
when(mockSalaryProcessor.paySalary()).thenReturn(TransactionStrategy.SUCCESS);
MyPaymentService myPaymentService = new MyPaymentService(mockSalaryProcessor);
assertThat(myPaymentService.sendPayment()).isEqualTo(PaymentStatus.SUCCESS);

模拟你无权掌控的类型会让测试维护工作更困难:

  • 第三方库的升级变得更困难:通常在 Mock 中硬编码了某个 API 的预期返回值,而这个值有可能被我们写错了,也可能随时间而过时。当升级第三方库的新版本时,手动更新测试例也需要花大量时间。

在上面的示例中,当一个新版本修改了“ addStrategy() ”,使其可以返回一个从“TransactionStrategy”派生出来的新类(例如“ SalaryStrategy ”)。即便被测试代码根本不需要修改(因为它仍旧是引用 TransactionStrategy ),可为了自动化测试能够成功,你还是要去修改你的 Mock 。

  • 很难知道第三方库的更新是否在你的代码中引入了 bug:随着第三方库的变更,你在 Mock 中所内置的原定假设可能过时了,因此导致:即使在被测代码有 bug 的情况下,测试也能成功通过。

在上面的示例中,如果第三方库将“ payalary() ”改为返回 TransactionStrategy.SCHEDULED ,由于被测代码未正确处理此返回值,就可能会引入错误。然而,作为使用这个第三方库的人,你可能并没接到变更通知。由于 Mock 不会返回这个新的值,所以你的测试代码仍旧成功执行。

所以,尽量不要使用 Mock ,而尽可能使用真实的实现代码。如果行不通的话,至少也应该使用由第三方库维护者所提供的伪实现( fake implimetation )。这会减少上面列出使用 Mock 所带来的维护成本,因为我们使用了真实的实现,上面由于 Mock 带来的维护问题不会发生。例如:

FakeSalaryProcessor fakeProcessor = new FakeSalaryProcessor(); // Designed for tests
MyPaymentService myPaymentService = new MyPaymentService(fakeProcessor);
assertThat(myPaymentService.sendPayment()).isEqualTo(PaymentStatus.SUCCESS);

如果你既不能使用真实的实现,也没有伪实现( 第三方库的维护者没有为你创建一个 fake implementation ),那么,你就要自己创建一个 wrapper 类来模拟 (Mock) 它了。虽然这并不很理想,但通过避免依赖于第三方库 API 的 Mock,也是可以减少一些维护负担。例如:

@Mock MySalaryProcessor mockMySalaryProcessor; // Wraps the SalaryProcessor library
...
// Mock the wrapper class rather than the library itself
when(mockMySalaryProcessor.sendSalary()).thenReturn(PaymentStatus.SUCCESS);

MyPaymentService myPaymentService = new MyPaymentService(mockMySalaryProcessor);
assertThat(myPaymentService.sendPayment()).isEqualTo(PaymentStatus.SUCCESS);

为了避免上面列出的问题,我们更愿意使用对真实实现的调用来测试 Wrapper 类。这样,使用实际实现进行测试的缺点(例如,测试运行时间较长)就被限制在这个 Wrapper 类的测试上,而不是整个代码库的测试。

“不要模拟( Mock )不属于你负责的组件!” 也在 Steve Freeman 和 Nat Pryce 写的 《Growing Object Oriented Software, Guided by Tests》一书中提到了。更多关于过度使用 Mocks 的缺点(甚至你自己的负责的类)可参见 这里.

如果你不想那么麻烦,也可以使用简单使用下面的方式初步判断一下。但是,由于它将问题过于简化了,所以仅作为参考。


原文作者:Stefan Kennedy and Andrew Trenk

原文链接:Don’t Mock Types You Don’t Own

发表时间:July 16, 2020


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK