初识mock
作为一个动词,mock
是模拟、模仿的意思;作为一个名词,mock
是能够模仿真实对象行为的模拟对象。
在软件测试中,mock所模拟的对象是什么呢?
它一定不是我们所测试的对象,而是 SUT 的依赖(dependency)。换句话说,mock 的作用是模拟 SUT 依赖对象的行为。
测试的对象一般称之为
SUT(Software Under Test)
文字不好理解,我们画个图,如下图所示,被测试对象是 A,A 依赖的是B,B 依赖的是 C。而我们要 mock 的是 B 的行为。图中 A 就是 SUT。
![](https://i-blog.csdnimg.cn/blog_migrate/ca752dc6690e2b3dcd135227653f8bcb.png)
为什么需要模拟 B 的行为呢?
(1)提高 A 的测试覆盖率。A 依赖 B,本质上依赖的是 B 的返回结果,也就是说 B 的返回结果会影响 A 的行为。通过 mock B 我们可以构造各种正常和异常的来自 B 的返回结果,从而更充分测试 A 的行为。
(2)避免 B 的因素从而对 A 产生影响。依赖真实的 B 去测试 A 可能有很多问题:B 的开发没有完成时无法测试 A;B 有阻塞性bug 时无法测试 A;B 的依赖 C 有阻塞性 bug 时无法测试 A;
(3)提高 A 的测试效率。B 的真实行为可能很慢,而 B 的模拟行为是非常快的,因此可以加快 A 的测试执行速度。
mock 种族
常见的 mock 类型如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/0545ad2b51dafa89ef6db0ef7ecc2f2e.png)
从下往上依次解释一下:
(1)
方法级别 mock
:mock 的对象是一个函数调用,例如获取系统环境变量。(2)
类级别 mock
:mock 的对象是一个类,例如一个 HTTP server。(3)
接口级别 mock
:mock 的对象是一个 API 接口。(4)
服务级别 mock
:mock 的对象是整个服务。比如前端工程师自测试时,可以讲后端整个服务都 mock 掉,这其实等同于将后端的所有接口都 mock。
接口mock注入的五种方式
在使用 mock 进行接口测试时,一般要做两件事情,即打桩
和调桩
。
其实打桩
就是创建mock 桩,指定 API 请求内容及其映射的 mock 响应内容;所谓调桩
就是被测服务来请求 mock 桩并接收 mock 响应。
事实上,在打桩
和调桩
之间还隐藏着一件不显山露水、但是及其重要的事情,那就是 mock 桩的注入(mock injection)。
什么是 mock 注入?
mock 的本质就是用模拟桩
来替换真实的依赖
。所谓 mock 桩注入
就是阻断被测服务与真实服务之间的链路,建立被测服务与 mock 之间的链路过程。
![](https://i-blog.csdnimg.cn/blog_migrate/c73e3014c05cede4a80c0bb0891039c8.png)
如何注入 mock?
总的来说 mock 桩的注入方式与架构、被测服务的架构等因素相关,在实际中常见的 mock 桩注入方式包括但不限于以下五种。
(1)API 请求构造
在 mock 接口中被测服务是 API 的请求方,即客户端;依赖服务是 API 的响应方,即服务端。根据 mock 工作的位置,mock 可以分为客户端 mock
和服务端 mock
。
客户端 mock
:mock 在被测服务内部工作,直接拦截被测服务的 API 请求方法(比如 HTTP Client方法),在被测服务调用 API 请求方法时,直接从方法内部返回预定义的 mock 响应。
服务端 mock
:mock 在被测服务外部工作,作为 HTTP 服务器接收被测服务发送的 API 请求,并返回预定义的 mock 响应。
客户端 mock 的注入其实就是改造被测服务的 API 请求方法,即在 API 请求方法中加入 mock 处理逻辑。当满足某些条件时执行 mock 分支,不满足时执行真实分支。
可以通过两种方式实现,一种是直接改造源代码
,另一种是利用字节码增强技术对字节码进行改造(Java 语言)
。
![](https://i-blog.csdnimg.cn/blog_migrate/11c8b966a3c25e356205fcc675578d49.png)
API 请求改造这种注入方式适用于客户端 mock,其优势性能极好,其不足是实现成本较高。
(2)本地配置
对于服务端 mock,打桩之后会生成唯一的 mock 桩地址。被测服务要想调用这个桩需要知道桩地址,如何让被测服务知道桩地址呢?一种最直接的方法就是被测服务提供一个依赖服务地址配置项,在需要使用 mock 时将依赖服务地址修改成 mock 地址。
本地配置的优势是实现简单,不足之处是修改配置项需要重启
被测服务,在需要进行 mock 服务与真实服务切换时不方便。
![](https://i-blog.csdnimg.cn/blog_migrate/628d175c760e4be61677cb9ec1ca1491.png)
(3)配置中心
在服务端 mock 中,为了避免修改依赖服务地址配置项导致被测服务重启,可以采用配置中心(如 Spring Cloud Config Server)存储和管理依赖服务地址配置,或者使用注册中心(如 Spring Cloud Eureka)记录服务与服务地址的映射关系。
使用配置或者注册中心时,mock 注入的方法是修改配置中心,将依赖服务地址改成 mock 地址。这种注入方法不需要重启被测服务,但是从配置改变到配置生效可以存在一定的延时
。
![](https://i-blog.csdnimg.cn/blog_migrate/c5163f8c08c6fa6b0161a2f30639c221.png)
(4)反向代理
在微服务架构下,被测服务与依赖服务之间可能不是直连的,而是经过了一层反向代理,例如 API 网关。在这种情况下,被测服务是通过调用 API 网关来间接调用依赖服务的接口。
在 API 网关模式下,mock 注入的具体做法就是修改 API 网关配置,将依赖服务 API 网关接口绑定的地址改成 mock 地址。
这种注入的优势是对被测服务无侵入
,并且实现更细粒度(接口级)的 mock。当然,根据 API 网关的实现不同,仍然可能存在一定的时延
。亚马逊 AWS 的 API 网关就是采用这种方式进行 mock。
![](https://i-blog.csdnimg.cn/blog_migrate/4ac9e5249c6784faa90ee88cd6be9fad.png)
(5)前向代理
服务端 mock 除了作为 HTTP 服务器,还可以兼备 HTTP 代理的功能,这种架构又叫做 mock 代理
,例如 mock server proxy
。对于 mock 代理来说,它不仅能够返回 mock 响应,而且能够在需要的时候将 API 请求转发给依赖服务,并将依赖服务的真实响应返回给被测服务。
使用前向代理模式,mock 注入的方式是将被测服务的依赖地址或网络代理修改为 mock 地址,这种注入方法需要重启被测服务
,其优势是能够实现细粒度的 mock,并且能够根据录制的真实响应自动生成 mock。
![](https://i-blog.csdnimg.cn/blog_migrate/91febbb367fceb7cdd051d27eb9b4831.png)
五种注入方式对比
一张表格总结一下
![](https://i-blog.csdnimg.cn/blog_migrate/af81bfcb5baad975dff25f658f9e73d3.png)
不可忽视的mock两大功能
关于 mock,经常容易被误解的是:认为 mock 只是模拟返回的结果而已。
实际上 mock 还可以提供两大功能:(1)记录真实的调用信息;(2)生成模拟的返回信息;
![](https://i-blog.csdnimg.cn/blog_migrate/63fd0d6848d02ca70fc76267efd51bbd.png)
对于测试用例来说,我们不仅关心 mock 是否返回了期望的结果,还需要关心 SUT 是否以期望的方式调用了 mock 对象。
如果 SUT 没有以期望的方式调用,比如:没有传参或者参数不对,那么 SUT 就存在问题。
mock 需要详细记录来自SUT 的调用信息,并提供给用例来校验。比如 Java mockito
就提供了此类校验功能:
List<String> mockedList = mock(MyList.class);
mockedList.size();
// 校验 size 函数调用且只调用了1次
verify(mockedList, times(1)).size();
常用 mock 工具
单元测试级别
这个级别的mock工具有easymock、jMock、Mockito、Unitils Mock、PowerMock、JMockit等,关于各自优劣势大家可以上网查询。
接口测试级别
接口级别的mock工具完成的主要功能是对一个用户的请求,模拟server返回一个接口的响应数据。常用的有:
Wiremock
Mockserver
Moco
Mock.js
RAP
mock 不是银弹
说了这么多 mock 的好处,实际上 mock 也有很多不足,比如:
(1)mock 可能导致问题遗漏。mock 的模拟行为与真实行为可能存在 GAP,导致基于 mock 的测试虽然通过了,但是基于真实对象的测试却失败了,这意味着问题被遗漏了。mock 很难模拟所有的真实情况。
(2)mock 带来较高的维护成本。基于 mock 的测试用例结构比较复杂,实现和维护都不容易,后期被测代码有变动时需要适配 mock 代码。
简单一句话:mock 不是银弹。
有态度的总结
mock 不是银弹,mock 是有利有弊的,一张图总结一下:
![](https://i-blog.csdnimg.cn/blog_migrate/7ab6a55f9c93df1dff113a038235521c.png)
说了这么多,在工作中如何正确使用 mock 呢?这里提两点建议,敲黑板啦。
(1)不要过度使用 mock。测试用例中掌握好使用 mock 的度。在涉及网络访问、数据库读写、操作系统交互等系统级调用,优先使用 mock。
(2)不要过度依赖基于 mock 的测试结果。基于 mock 的测试无论多么充分,这都不能保证不出现问题的遗漏。一个完整的测试策略一定是由基于 mock 的测试和基于非 mock 的测试共同组成的,二者相辅相成缺一不可。
特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:
长按订阅更多精彩▼
如有收获,点个在看,诚挚感谢