0%

不要过度使用mock

当我们在写测试用例时,通过mock来忽略待测代码的依赖看起来是很简单的.

1
2
3
4
5
6
7
8
9
10
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有时会使测试用例更简单,也更有效果.

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

过度使用mocks可能会引起诸多问题:

  • 测试变得更加难以维护了.当你实现mock逻辑的时候,你是在把代码的实现细节漏进测试的代码里.当业务代码中的实现细节变了的时候,你也需要更新测试用例的mock逻辑以反映最新的状态.测试用例应该尽量少了解代码的实现,且应该专注于测试代码的公有接口.
  • 测试对代码正常工作提供的保证是很少的.当你告诉你的mock应该如何表现时,获得的唯一保证是:只有在你的mock与真实实现一致时,才能保证线上的代码是经过验证的.这是非常难以保证的事,并且随着时间的推移会变得越来越难,主要是因为真实实现的行为会逐渐与mock不一致.
    一些迹象可以用以辅助判断”是否过度使用了mock”, 比如当你不只是mock一两个类的时候, 或者是当你的mock需要明确指定”怎么做”而不是”做什么”的时候.

如果你尝试阅读一个使用了mock的测试用例时,发现自己不自主的跳进了待测代才能理解测试用例时, 那么你可能就是过度使用mock了
有时,你没办法在测试中使用一个真实实现(比如,太慢或者需要使用网络),但有比mock更好的选择,比如hermetic server(比如一个信用卡服务,在本机启动,服务于你的测试)或是一个fake实现(例如一个内存版的信用卡服务).
关于hermetic更多的信息,参考http://googletesting.blogspot.com/2012/10/hermetic-servers.html.

Great Q&A

Q1: mocks的问题是复制了一遍业务代码逻辑-立刻违反了DRY原则.必须要使用mocks是一种”code smell”, 通常意味着需要重构了.
A1: Mocks在不包含逻辑,只是为了让你的代码到达指定状态的时候工作的很好(比如,你对于依赖组件返回的list为空有特殊处理时,你可以使用mock来让依赖组件返回空列表,以保证测试可以覆盖到这样的场景). 这本质上是stubs. from coderfengyun Mocks在测交互的时候也表现很好:http://googletesting.blogspot.com/2013/03/testing-on-toilet-testing-state-vs.html, 这本质上类似于spy from coderfengyun

Q2: 单元测试的要点就是你试图只测试功能的一小部分. 一个单元通常被理解为一个类. 然而, 有时这个类用来与文件,数据库,网络,或其他类打交道, 把这些依赖一股脑拉近测试,会是的单元不再明晰. 用mock来处理与其他系统间的交互,是我理解的对于mock比较好的用法. 有一个场景是无法避免使用mock的: 测试与人交互之后代码的行为. 我最近mock了一个对话框服务, 以使得我可以mock用户选择了’yes’或是’no’. 如果你想要去自动化测试与人的交互相关的,mocking是你唯一的选择.
A2: 单元更好的定义是一个独立的类或是一组相关的类. 如果不这么定义,当你把一些util性质的代码从一个类中重构到一个helper类时, 这个helper类就成为了一个独立的单元, 意味着之前只是实现细节的现在成为了你必须要mock的东西, 这也十分容易导致测试用例难以维护.
即使是在用mock来分离独立单元的场景下, mocking有时也会使事情变得极其复杂. 只有在要mock的接口极其简单时,mock才会真正有意义(比如,如果你的代码调用了一个方法,这个方法返回了一个list,你的代码对这个list做了些复杂处理,你可以用mock来返回一个空list,只包含一个元素的list,等等). 但如果有更多更复杂的交互, 需要多个mock来互相操作, 或是需要mock来返回其他mock, 更好的办法是用一个fake, 或是干脆直接用真实实现(如果TA并没有那么的重).

翻译自

https://testing.googleblog.com/2013/05/testing-on-toilet-dont-overuse-mocks.html