涨薪技术|深入接口测试之Mock技术

涨薪技术|深入接口测试之Mock技术 01为什么要用Mock服务端与客户端约定了接口但服务端还没有完成开发时客户端一般由如下处理方式1、在程序中写模拟数据程序中增加垃圾代码后期还要删除可能对代码造成影响模拟异步请求不方便服务端接口开发完成后需要重新书写网络请求部分的代码JS、iOS、Android 多前端需要分别模拟数据重复工作量2、使用 Nginx、http-server 等 WebServer只支持 GET 请求无法针对请求进行校验或特定处理3、自己写简单的程序模拟返回数据开发期间 API 还处于频繁变动时期持续调整成本较高以上方法均不方便做单元测试对于需要演示的场景没有后端业务服务器支撑无法演示客户端改造成本高所以要引入Mock技术02Mock使用场景在使用Mock的过程中发现Mock是有一些通用性的对于一些应用场景是非常适合使用Mock的1)真实对象具有不可确定的行为(产生不可预测的结果如股票的行情)2)真实对象很难被创建(比如具体的web容器)3)真实对象的某些行为很难触发(比如网络错误)4)真实情况令程序的运行速度很慢5)真实对象有用户界面6)测试需要询问真实对象它是如何被调用的(比如测试可能需要验证某个回调函数是否被调用了)7)真实对象实际上并不存在(当需要和其他开发小组或者新的硬件系统打交道的时候这是一个普遍的问题)当然也有一些不得不Mock的场景8)一些比较难构造的Object这类Object通常有很多依赖在单元试中构造出这样类通常花费的成本太大;9)执行操作的时间较长Object有一些Object的操作费时而被测对象依赖于这一个操作的执行结果例如大文件写操作数据的更新等等出于测试的需求通常将这类操作进行Mock;10)异常逻辑一些异常的逻辑往往在正常测试中是很难触发的通过Mock可以人为的控制触发异常逻辑;11)在一些压力测试的场景下也不得不使用Mock例如在分布式系统测试中通常需要测试一些单点(如namenodejobtracker)在压力场景下的工作是否正常。而通常测试集群在正常逻辑下无法提供足够的压力(主要原因是受限于机器数量)这时候就需要应用Mock去满足;12)在mock点的选择过程中以下的一些点会是一些不错的选择 网络交互如果两个被测模块之间是通过网络进行交互的那么对于网络交互进行Mock通常是比较合适的如RPC;13)外部资源比如文件系统、数据源如果被测对象对此类外部资源依赖性非常强而其行为的不可预测性很可能导致测试的随机失败此类的外部资源也适合进行Mock;14)UI因为UI很多时候都是用户行为触发事件系统本身只是对这些触发事件进行相应对这类UI做Mock往往能够实现很好的收益很多基于关键字驱动的框架都是基于UI进行Mock;15)第三方API当接口属于使用者通过Mock该接口来确定测试使用者与接口的交互。当然如何做Mock一定是与被系统的特性精密关联的一些强制性的约束和规范是不合适的。03Mock工具的选择Mock测试工具分为单元测试级别的Mock工具和接口测试级别的Mock工具。一、单元测试级别的Mock工具目前这个级别的Mock工具有easymock、jMock、Mockito、Unitils Mock、PowerMock、JMockit等等。就目前来讲是mockitPowerMock、JMockit这两种工具使用人数较多。JMockit的功能最为完善mockitPowerMock的用户体验相对较好一点。二、接口测试级别的Mock工具接口级别的Mock工具完成的主要功能是对一个用户的请求模拟server返回一个接口的响应数据。目前这类的主流mock工具主要有以下几种1Wiremock特点 - 支持Http响应头,匹配URL,head和body内容模式 - 请求验证 - 可以作为一个独立的进程或者WAR app在单元测试中运行 - 可以通过Java APIJson文件和JSON over HTTP配置 - 有记录/回放功能 - Fault injection - 可以作为请求检查和替换的浏览器代理 - 有状态的行为模拟 - 可配置响应延迟2Mockserver​​​​​​​特点 - 能够mock HTTP或者HTTPS 的server或者服务 - 当一个请求匹配 expectation时能够返回一个mock response - 当一个请求匹配 expectation时能够forward 一个请求 - 当一个请求匹配 expectation时能够执行一个回调 callback允许动态地创建response - 支持Request验证3)Moco4)Mock.js5)RAP这些了解即可04搭建Mock平台这里使用wiremock环境搭建Mock平台具体方法如下WireMock 的独立安装版本其实就是一个Jar包可以从Maven仓库中下载或者也可以在Java Maven项目中通过 pom.xml的依赖添加​​​​​​​dependency groupIdcom.github.tomakehurst/groupId artifactIdwiremock-jre8- standalone/artifactId version2.26.3/version scopetest/scope /dependency除了Standalone 版本 也可以直接在 Java代码中引用对应Maven依赖​​​​​​​dependency groupIdcom.github.tomakehurst/groupId artifactIdwiremock-jre8/artifactId version2.26.3/version scopetest/scope /dependencyWireMock 命令行用法WireMock 的独立版本是一个 Jar 包所以运行 Wiremock 自然是需要 Java 基础环境命令如下java -jar wiremock-standalone-2.26.3.jar命令行支持的一些主要参数及其作用说明如下--port 设置 Mock 服务的 http 端口默认启动在 8080 端口如果设置为0则自动确定端口--https-port 设置 https 的端口--verbose 在终端显示详细的日志信息--root-dir 设置 mappings 和 __files 目录的工作路径--enable-browser-proxying 以浏览器代理的方式运行单来说WireMoc 会在本地启动一个侦听指定端口的web服务这里指定的端口可以用 --port 指定http协议 或 --httpsport指定https协议端口。启动后我们发到指定端口的请求就会由WireMock来完成响应达到接口Mock的目的命令行启动后如图启动后我们在本地运行目录下会看到 WireMock会自动生成__files 和 mappings两个目录。这两个目录中存放的就是Mock模拟的接口匹配内容了。__files 存放接口响应中会用到的一些文件资源mappings 存放接口响应匹配规则具体的匹配方法我们后面再介绍这里先介绍下WireMock本身自带的Admin接口。Wiremock Admin 接口WireMock 本身支持管理接口启动后根据启动的端口访问http://localhost:8080/__admin/docs/ 可以看到如下两个链接​​​​​​​OpenAPI 3.0 spec Swagger UI可以用 Swagger UI接口定义格式查看支持的管理接口http://localhost:8080/__admin/swagger-ui/05WireMock基本用法1)在mappings下创建一个*.json文件​​​​​​​{ request: { method: GET, url: /api/mocktest }, response: { status: 200, bodyFileName: response.json, headers: { Content-Type: application/json, Cache-Control: max-age86400 } } }bodyFileName还可以是html、xml等类型的文档。2)在__files下创建响应文件上例中response.json 就是需要我们在__files里面建立的响应文件。其内容为​​​​​​​{ test:wiremock }在浏览器或者使用curl命令调用http://localhost:8080/api/mocktest 就能返回test.json的内容了。当然你也可以不用创建这个文件直接在request中将bodyFileName改成直接body写出这个json也可以​​​​​​​{ request: { method: GET, url: /api/mocktest }, response: { status: 200, body: {\test\:\wiremock\}, headers: { Content-Type: application/json, Cache-Control: max-age86400 } } }06Wiremock支持的HTTP方法1POST​​​​​​​http://localhost:8080/api/products {request: { method: POST, url: /api/products, bodyPatterns: [{ equalToJson: { \name\: \new product\, \creator\: \tester\, \createTime\: \2015-09-07\ }, ignoreArrayOrder : true, ignoreExtraElements : true }] }, response: { status: 201, body: Add successfully., headers: { x-token: xxxxxxxxxxxxxxxxxxxxxxxxxxxx } } }bodyPatterns 请求正文的匹配模式包括equalToJson 匹配JSON数据 ignoreArrayOrder 是否区分JSON数组元素顺序ignoreExtraElements 是否忽略额外的元素。2PUT​​​​​​​http://localhost:8080/api/products/1 { request: { method: PUT, url: /api/products/1, bodyPatterns: [{ equalToJson: { \id\: 1, \name\: \new product\, \creator\: \tester\, \createTime\: \2015-09-07\ }, ignoreArrayOrder : true, ignoreExtraElements : true }] }, response: { status: 200, body: Update successfully., headers: { x-token: xxxxxxxxxxxxxxxxxxxxxxxxxxxx } } }3) DELETE​​​​​​​http://localhost:8080/api/products/1 { request: { method: DELETE, url: /api/products/1 }, response: { status: 204, headers: { x-token: xxxxxxxxxxxxxxxxxxxxxxxxxxxx } } }4URL Matching​​​​​​​http://localhost:8080/api/products/12/3… { request: { method: GET, urlPattern: /api/products/[0-9] }, response: { status: 200 } }5Query参数匹配​​​​​​​http://localhost:8080/api/products? searchchina { request: { method: GET, urlPath: /api/products, queryParameters: { search: { contains: chin } } }, response: { status: 200, headers: { Content-Type: application/json }, body: { \id\: 7, \name\: \shan zai\, \from\:\China\ },{ \id\: 7, \name\: \shan zai\, \from\:\China(RPC)\ } } }6模拟错误​​​​​​​{ request: { url: /unknown.html, method: GET }, response: { status: 404, headers: { Content-Type: text/html; charsetutf- 8 } } }7设置响应延时​​​​​​​{ request: { method: GET, url: /delayed }, response: { status: 200, bodyFileName: response.json, headers: { Content-Type: application/json, Cache-Control: max-age86400 }, fixedDelayMilliseconds: 2000 } }