0%

不要Mock哪些不属于你的类型

下面这段代码mock了一个第三方类库。这么做会有什么问题呢?

1
2
3
4
5
6
7
8
//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);

Mocking哪些不属于你的类型将使得维护变得更加困难

  • 这可能会使得升级到该类库的新版本更加困难:硬编码在测试里的对于API的假设,可能会出错或是过时。在需要升级的时候,可能需要大量耗时的手工更新这些mock逻辑。在上面的例子中,如果该类库将addStrategy()的返回值修改为TransactionStrategy的一个子类(比如SalaryStrategy),就需要所有测试中的mock更新到这个类型,尽管待测代码是不需要修改的,因为待测代码仍然可以使用TransactionStrategy。
  • 这可能使得判断该类库的更新是否引入Bug变得更加困难。随着三方类库不停迭代,写成mock的假设可能会过时,会使得虽然测试通过但待测代码中存在着Bug。在上面的例子中,如果该类库将paySalary()的反馈值改为TransationStrategy.SCHEDULED,由于待测代码没有正确的处理这个返回值,一个潜在的Bug就产生了。然而,待测代码的维护者无法感知到问题,因为mock没有返回这个值,且测试用例仍然是通过的状态。

与其使用mock,不如使用真实实现,如果真实实现不可行,那么就用一个fake实现,该fake实现需要由类库的作者来提供。这使得维护的成本得以降低,因为上面列出来的mock可能会引入的问题不会在一个真实实现或fake实现中出现。例如:

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

如果无法在测试中使用真实实现,而且fake实现也不存在(并且类库的维护者也没有创建一个fake),创建一个wrapper类来调用类库中的类,然后mock这个wrapper类。这减轻了维护负担,因为测试代码中的mock不再依赖第三方类库的(方法)签名,因此一旦对方签名变化是也不会产生散弹式修改。例如:

1
2
3
4
5
6
7
@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);

为了规避上述提到的问题,首选测试wrap类+真实第三方类库实现。如果这么搞,测试三方类库真实的缺点(比如,非常慢)就会被限制在对于wrapper类的测试用例上,而不是影响整个代码库的测试执行。如果没有进行wrap类+第三方类库的真实/fake实现,那么只能解决第三方代码签名升级带来的修改问题,而无法解决当三方代码升级而mock假设不对时,可能带来的潜在bug问题。

“Don’t mock types you don’t own”也被Steve Freeman和Nat Pryce在他们的书Growing Object Oriented Software, Guided by Tests。关于过度使用mock的缺点(即使是用来mock你自己的类),可以看看Google Testing Blog的这篇文章

翻译自

https://testing.googleblog.com/2020/07/testing-on-toilet-dont-mock-types-you.html