作者:Andrew Trenk
原文链接:http://googletesting.blogspot.tw/2013/05/testing-on-toilet-dont-overuse-mocks.html
在编写测试代码的时候,使用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);
}
不过,如果我们不用Mock来实现测试代码的话,有时测试代码看起来会更加简单有效。
public void testCreditCardIsCharged() {
paymentProcessor = new PaymentProcessor(creditCardServer);
paymentProcessor.processPayment(creditCard, Money.dollars(500));
assertEquals(500, creditCardServer.getMostRecentCharge(creditCard));
}
滥用Mock会带来不少问题:
- 测试代码可读性变差。比起只是直接调用你的代码(例如,直接把参数值传递给被测方法,然后检查输出),你需要编写额外的代码以告诉Mock类如何去执行。这些额外的代码削弱了你实际的测试意图,尤其是在你不了解产品代码的内在实现的情况下,这些代码的可读性通常很差。
- 测试代码可维护性变差。当你告诉Mock类如何执行时,你就会把产品代码中的一些实现细节也带入到测试代码中来。而一旦产品代码的实现发生了改变,你可能就需要对测试代码进行更新以适应产品代码的变更。而我们知道,典型的测试代码应该与被测试产品代码松耦合,并且测试代码应该着眼于被测代码的外部接口(而不是他们的实现)。
- 测试代码对被测产品质量的保证水平也会降低。当你告诉Mock类如何执行,那么只有被测试代码严格符合你在Mock中描述的所有行为时,测试结果才正确。而这一点很难保证,而且随着时间的变化这个问题将变得更严重,因为Mock代码和实际的实现要做到同步是很困难的。
你在滥用Mock的一些迹象:你试图在一个测试中Mock过多(超过一两个)的类,或者你的Mock方法定义了过多(超过一两个)方法行为。亦或是,如果在读Mock过的测试代码时,你发现需要了解被测产品代码的实现才能读懂,那么你可能在滥用Mock。
有时你很难在测试引用真实的依赖对象(例如,可能使测试很慢,或者测试依赖于网络),但是在使用Mock之外你仍然有很多更好选择,例如使用独立的本地服务(例如,一个专为测试而启动在本地的模拟信用卡服务)或者一个产品代码的伪实现(例如,一个驻留在内存中的信用卡服务)。