单元测试-mock浅析-1-概述

简介

在软件测试过程中,测试的对象一般称为 SUT(Software Under Test)

对于 被测试对象A,A依赖B,B依赖C …

这里的 A依赖B 实际上就是 A依赖于B的返回结果。

在测试过程中 这种依赖 造成了主要造成了以下三种情况:

  1. 无法完全控制 B的返回结果,难以无法覆盖到 A设计的全部分支,难以充分测试 A的行为
  2. 无法避免 B的因素产生的影响,例如:B的开发尚未完成、B有阻塞性Bug、B的依赖C出现了阻塞性Bug
  3. B的真实行为可能非常慢

因此我们需要模拟SUT依赖对象B的行为,而mock是能够模仿真实对象行为的模拟对象。

运用了mock以后,我们解决不同的单元之间由于耦合而难于开发测试的问题,同时:

  1. 提高 A 的测试覆盖率
  2. 提高 A 的测试可用性
  3. 提高 A 的测试效率

但是:

  1. 无法完全模拟真实行为
  2. 用例实现成本高
  3. 用例维护成本高

mock 的种类

mock有以下4种:

  1. 方法级别:mock 的对象是一个函数调用,例如获取系统环境变量。
  2. 类级别:mock 的对象是一个类,例如一个 HTTP server。
  3. 接口级别:mock 的对象是一个 API 接口。
  4. 服务级别:mock 的对象是整个服务。比如前端工程师自测试时,可以讲后端整个服务都 mock 掉,这其实等同于将后端的所有接口都 mock。

mock 的注入

mock 的本质就是用模拟桩来替换真实的依赖。所谓 mock 桩注入就是阻断被测服务与真实服务之间的链路,建立被测服务与 mock 之间的链路过程。

在 mock 接口中被测服务是 API 的请求方,即客户端;依赖服务是 API 的响应方,即服务端。根据 mock 工作的位置,mock 可以分为客户端 mock 和服务端 mock。

可以通过两种方式实现,一种是直接改造源代码,另一种是利用字节码增强技术对字节码进行改造(Java 语言)。

请求改造

mock 在被测服务内部工作,直接拦截被测服务的 API 请求方法(比如 HTTP Client方法),在被测服务调用 API 请求方法时,直接从方法内部返回预定义的 mock 响应。

客户端 mock 的注入其实就是改造被测服务的 API 请求方法,即在 API 请求方法中加入 mock 处理逻辑。当满足某些条件时执行 mock 分支,不满足时执行真实分支。

例如:

真实请求时 前端通过client发起 url=“http:\\localhost:8080\test” 的请求

测试请求时 前端通过client发起 url=“http:\\localhost:8080\test?mock=true” 的请求

修改client的代码,client在发起请求前检查参数,发现有mock=true就转向 mock分支,否则 发起真实请求

这种注入方式适用于客户端 mock,其优势性能极好,其不足是实现成本较高。

本地配置

被测服务提供一个依赖服务地址配置项,在需要使用 mock 时将依赖服务地址修改成 mock 地址。

本地配置的优势是实现简单,不足之处是修改配置项需要重启被测服务,在需要进行 mock 服务与真实服务切换时不方便。

配置中心

在服务端 mock 中,为了避免修改依赖服务地址配置项导致被测服务重启,可以采用配置中心存储和管理依赖服务地址配置,或者使用注册中心记录服务与服务地址的映射关系。

使用配置或者注册中心时,mock 注入的方法是修改配置中心,将依赖服务地址改成 mock 地址。这种注入方法不需要重启被测服务,但是从配置改变到配置生效可以存在一定的延时。

反向代理

在微服务架构下,被测服务与依赖服务之间可能不是直连的,而是经过了一层反向代理,例如 API 网关。在这种情况下,被测服务是通过调用 API 网关来间接调用依赖服务的接口。

在 API 网关模式下,mock 注入的具体做法就是修改 API 网关配置,将依赖服务 API 网关接口绑定的地址改成 mock 地址。

这种注入的优势是对被测服务无侵入,并且实现更细粒度(接口级)的 mock。当然,根据 API 网关的实现不同,仍然可能存在一定的时延。亚马逊 AWS 的 API 网关就是采用这种方式进行 mock。

前向代理

服务端 mock 除了作为 HTTP 服务器,还可以兼备 HTTP 代理的功能,这种架构又叫做 mock 代理,例如 mock server proxy。对于 mock 代理来说,它不仅能够返回 mock 响应,而且能够在需要的时候将 API 请求转发给依赖服务,并将依赖服务的真实响应返回给被测服务。

使用前向代理模式,mock 注入的方式是将被测服务的依赖地址或网络代理修改为 mock 地址,这种注入方法需要重启被测服务,其优势是能够实现细粒度的 mock,并且能够根据录制的真实响应自动生成 mock。

mock 提供的功能

  1. 记录真实的调用信息;

  2. 生成模拟的返回信息;

对于测试用例来说,我们需要关心 SUT 是否以期望的方式调用了 mock 对象。 如果 SUT 没有以期望的方式调用,比如:没有传参或者参数不对,那么 SUT 就存在问题。mock 需要详细记录来自SUT 的调用信息,并提供给用例来校验。

其次,我们需要关心 mock 返回结果后,被测试对象执行是否符合预期
·

参考文献:

  1. 雷架.干货!用大白话告诉你什么是Mock测试.[OL].https://developer.51cto.com/article/647732.html