
字数:1235,时间:3分钟

01
mock的定义
在软件开发中,我们不可避免的要调用一些外部或者系统级别的接口,然而,我们在测试时,也许这些接口或环境并不存在。比如在对我们自己的模块做单元测试时,发现自己的模块依赖的别的模块或接口还没有建立好,如何测试?
Mock概念应运而生,最开始在Java领域,后来各种语言和开发领域均引入该概念。mock 的意思是模拟某个对象的行为。
在软件测试中,mock模拟的对象是什么呢?它一定不是我们所测试的对象(software under test, SUT),而是SUT的依赖。也就是说,mock的作用是模拟SUT依赖对象的行为。
如下图所示,被测对象是A,A的依赖是B,B的依赖是C。mock模拟的是B的行为。

为什么需要模拟B的行为呢?
(1) 提高A的测试覆盖率。A依赖B,本质上依赖的是B的返回结果。也就是说,B的返回结果会影响A的行为。通过mock B,我们可以构造各种正常和异常的返回结果,从而更充分测试A的行为。
(2) 避免B的因素影响对A的测试。不管是传统的单体应用,还是现在流行的微服务,这点都特别重要,因为任何外部依赖的存在都会极大的限制测试用例的可迁移性和稳定性。
(3) 提高A的测试效率。任何外部服务调用至少是跨进程级别的消耗,甚至是跨系统、跨网络的消耗,而Mock可以把消耗降低到进程内。
02
常用的mock框架

03
mock的两大功能
关于mock,经常会认为mock只是模拟返回结果而已。实际上,mock需要提供两大功能:
1) 记录真实的调用信息
2) 生成模拟的返回信息

对于测试用例来说,我们不仅要关心mock是否返回了期望的结果,还需要关心SUT是否以期望的方式调用了mock。如果SUT没有以期望的方式调用,例如没有传参或参数不对,那么SUT就存在问题。
mock需要详细地记录SUT的调用信息,并提供给用例用来校验。例如,Java mockito就提供了此类校验功能:
List<String>mockedList=mock(MyList.class);
mockedList.size();
verify(mockedList, times(1)).size(); //校验size()函数调用且只调用了1次
04
mock的不足
说了这么多mock的好处,实际上mock并不是万能的。mock也存在一些不足,例如:
1)mock可能导致问题遗漏。mock的模拟行为与真实行为可能存在差异,导致基于mock的测试虽然通过了,但是基于真实对象的测试却失败。这意味着问题被遗漏了。
2) mock带来较高的维护成本。一方面,基于mock的测试用例结构复杂,实现和维护都不容易;另一方面,基于mock的测试用例与SUT的实现细节存在耦合,后期SUT实现方式改变时,用例需要重新适配。
05
总结
一张表总结mock的利弊如下:

那么,在测试工作中,如何正确地使用mock呢?这里总结两点建议:
1) 不要过度使用mock。测试用例中,掌握好使用mock的度。在涉及到网络访问、数据库读写、操作系统交互等系统级调用,并且调用结果对SUT的处理逻辑有显著影响时,优先使用mock。
2) 不要过度依赖基于mock的测试结果。基于mock的测试无论如何充分,都不能保证不发生问题遗漏。一个完整的测试策略,一定是由基于mock的测试和基于非mock的测试共同组成的,二者相辅相成,缺一不可。
送上更多精彩好文
聊一聊小程序消息通知
测试接口,你需要先搞懂这些!
如何制定测试策略
redis的基础配置及集群模式
测试也应该懂的Nginx
缓存那些事儿
点“在看”给我一朵小黄花