奇技指南
Mock是指在测试过程中,对于一些不容易构造/获取的对象,创建一个Mock对象来模拟对象的行为。本文主要讲述Web接口的Mock。
本文来自公众号花椒技术。
什么是Mock?
Mock是指在测试过程中,对于一些不容易构造/获取的对象,创建一个Mock对象来模拟对象的行为。早期Mock多被用于单元测试/接口测试中,被测试对象依赖其他对象,且这些对象的构造复杂、耗时或者根本无法构造(未交付), 对于单个测试对象,假定其依赖对象的逻辑正确,我们只需要保证测试模块内部逻辑的质量即可。本文主要讲述Web接口的Mock。
接口Mock用处?
在实际的软件开发过程中,我们的链路往往是:服务业务A->服务业务B->客户端/前端->测试,整个开发周期里,业务B的人依赖业务A才能联调提测,客户端/前端依赖B的接口有数据后才能开发新的功能,Mock很好缩短了这个过程中等待的时间。
1. 客户端/前端开发联调前置,现今移动端的app多依赖服务端接口的返回来开发app的页面,在接口未开发完成的情况下,需要等待接口的数据来进行开发,这时候完善的的接口Mock服务能大大缩短开发联调等待时间。
2. 接口集成测试,部分依赖服务未完成前,利用Mock完成本身的接口开发/测试
3. 复杂的场景模拟,复现验证bug的时候,需要先准备比较复杂的数据场景,才能复现一个bug,此时,Mock的定制返回,节省了大量数据准备的时间,直接可复现和验证bug
4. 测试时,异常场景的模拟,如长字符串,负数,异常返回等
花椒的Mock方案
说了这么多,我们来说说花椒的接口Mock方案。传统的接口Mock服务弊端有:
需要绑定接口请求的业务服务到指定的Mock服务,这样需要wifi的host不停更改,或本机的host不停更改,来切换正常环境和Mock环境
绑定host因为是整个域名绑定到Mock服务,如果其他接口没有Mock会导致很多接口不可用,而走不到想走的场景,且多人公用wifi的情况下,会互相影响
不同的用户对同一接口的请求,期望的返回不一样,无法对用户定制化,也即同时只能满足一个开发或测试人员的Mock需求
传统的Mock服务大多采用文件编辑Mock数据,不易编辑管理
考虑到上述弊端,我们的接口Mock服务,设计的初衷有以下几点:
如何让客户端/前端开发人员简单易用,不需要太多环境的设置,保证用户能在正式环境和Mock环境之间切换
如何支持多用户同时使用,且Mock数据不一样的需求
花椒的部分服务是有加解密的,返回的数据是一堆加密串,如何更方便的编辑管理Mock返回数据
花椒的接口Mock方案, 主要是采用业务服务器跳转 + Mock服务 + 后台管理,同时支持传统的Mock服务的使用方式。
主要有如下三部分组成:
Mock后台
Mock后台是整个Mock服务的配置中心, 用户在后台定制自己的接口Mock数据,配置需要跳转的用户及接口。开发框架用的是springboot + mybatis + vue,前端页面和后端服务剥离,springboot提供操作数据库的接口给前端vue页面调用,主要文件目录一个vueMockController, 一个数据库操作文件MockMapper,文件目录非常简单,主要功能有:
1. 展示用户的所有Mock数据,支持根据作者查询,uri查询,模块查询
1/按Uri查找数据 2@RequestMapping("findByUri") 3public RestResult findByUri(@RequestBody JSONObject request) throws Exception { 4 int offset = request.getIntValue("offset"); 5 String uri = request.getString("uri"); 6 uri = "%" + uri + "%"; 7 List<MockData> data = mapper.getMockDataByUri(uri, offset); 8 for (MockData one : data) { 9 one.decryptData();10 }11 int total = mapper.getMockDataByUriTotal(uri);12 return success(formatPageData(data, total, offset));13}
2. 支持新增,删除、编辑Mock数据功能,写入数据根据选择的平台ios/android,判断有无加密,有加密按各自的加密key加密后写requset和response数据到数据库
主要数据库信息如下, encrypt是否加密,platform(请求客户端iOS或Android),model为模块,uid为用户私有标识,request为请求参数(同时支持key=value的form数据,{“key”:“value”}的json数据),response为响应数据
1TABLE `mock` ( 2 `id` bigint(20) NOT NULL AUTO_INCREMENT, 3 `uri` varchar(255) DEFAULT NULL, 4 `request` varchar(500) CHARACTER SET utf8mb4 DEFAULT NULL, 5 `response` text CHARACTER SET utf8mb4, 6 `statusCode` int(11) DEFAULT NULL, 7 `platform` varchar(20) CHARACTER SET utf8mb4 DEFAULT NULL, 8 `encrypt` tinyint(1) DEFAULT NULL COMMENT '0无 1经济 2底层', 9 `model` varchar(20) CHARACTER SET utf8mb4 DEFAULT NULL,10 `author` varchar(20) CHARACTER SET utf8mb4 DEFAULT NULL,11 `moduser` varchar(20) CHARACTER SET utf8mb4 DEFAULT NULL,12 `server` tinyint(1) unsigned DEFAULT NULL,13 `method` tinyint(1) unsigned DEFAULT NULL COMMENT '0Get 1Post',14 `uid` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL,15)
对应的页面展示如下,同时提供了加完数据后运行查看是否Mock成功的功能.
3. 获取业务服务器所有Mock配置,并展示,支持新增,编辑,修改后同步设置到业务服务器
Mock Service
Mock service是Mock请求/返回处理的中心模块,主要逻辑是获取跳转过来的用户请求,处理请求数据,根据不同的请求参数,查询数据库里的配置数据,修改response数据为Mock数据返回给用户。开发框架是基于git上开源项目Moco的二次开发, Moco在git上的开源地址https://github.com/dreamhead/moco, 是一个简单搭建模拟服务器的程序库/工具, 基于java开发的,Mock数据通过文件管理,使用方式很便捷简单,但由于本身花椒服务的特殊性,和考虑到可视化管理,我们对Moco进行了二次开发,主要改动方向:
1. Mock数据管理新增数据库存储管理方式, mybatis连接数据库,新增一个数据库操作MockMapper文件,新增一个service方法来处理数据库查询合并结果,具体的mybatis的配置如下:
1<configuration> 2 <environments default="development"> 3 <environment id="development"> 4 <transactionManager type="JDBC" /> 5 <!-- 配置数据库连接信息 --> 6 <dataSource type="POOLED"> 7 <property name="driver" value="com.mysql.jdbc.Driver" /> 8 <property name="url" value="jdbc:mysql://10.14.*.*:port/db_name?useUnicode=true&amp;characterEncoding=utf-8" /> 9 <property name="username" value="username" />10 <property name="password" value="password" />11 </dataSource>12 </environment>13 </environments>14 <mappers>15 <mapper class="com.github.dreamhead.moco.MockMapper"/>16 </mappers>17</configuration>
2. 请求处理适应花椒业务,url请求参数和post数据同时处理,匹配Mock数据,以及匹配优先级等特殊处理
修改moco-core工程里com.github.dreamhead.moco.internal下的MocoHandler.java方法doGetHttpResponse(),匹配规则:
优先匹配用户私有的Mock数据,uri(接口uri)+ platform + uid + request参数,有则返回
其次匹配非用户私有的Mock数据,uri(接口uri)+ platform + request参数,有则返回
忽略请求参数匹配Mock数据,uri(接口uri)+ platform,有则返回
忽略端平台匹配Mock数据,uri(接口uri),有则返回
此处之所以要做这么多优先级规则,是为了让使用Mock服务的接口能正常匹配到数据,大部分使用者在初期并没有私有数据的需求,随着场景的加深,才会设计独有的数据,所以根据使用习惯,做了分层匹配
3. https的方案更改为nginx配置处理业务证书,证书验证剥离Mock服务
java -jar mock-1.0.0-uber.jar http -p 3001 -c config.json //启动服务
本机nginx配置:
1server { 2 listen 443 ssl http2 default_server; 3 server_name *.*.com; //服务根域名 4 root /usr/share/nginx/html; 5 6 ssl_certificate "/etc/nginx/ssl/tongpei.*.*.com_bundle.crt"; //证书 7 ssl_certificate_key "/etc/nginx/ssl/tongpei.*.*.com.key"; //key 8 ssl_session_cache shared:SSL:1m; 9 ssl_session_timeout 10m;10 ssl_ciphers HIGH:!aNULL:!MD5;11 ssl_prefer_server_ciphers on;1213 # Load configuration files for the default server block.14 include /etc/nginx/default.d/*.conf;1516 location / {17 proxy_pass http://127.0.0.1:3001; //跳转mock服务18 }19
业务服务器跳转处理
此处的业务服务器是app应用/前端实际使用的服务器,比如登陆服务。业务服务器的跳转处理,主要是根据用户的请求,判断是否需要跳转到Mock服务。逻辑处理是在nginx层处理的,用的luascript,提供了两个接口,一个lua/pour,用于提供给Mock后台来设置跳转信息,写到Nginx缓存里;一个lua/get,用来获取nginx现有的配置。
1、提供mock跳转配置设置/获取接口
示例
1 curl 'http://proxy.**.com/lua/pour?project=test&group=mock&ttl=' -H "Cookie: auth=lua" -d 'value={"user/1**":{"4--60":true,"4--61":true}}'23 curl 'http://proxy.**.com/lua/get?project=test&group=mock'
参数
group: 写死为mock,表示Mock配置
project: 项目名称,比如live、test等
ttl: 这个配置的过期时间,默认是3600秒
value: 配置信息,是一个json串,详见以下说明
对于配置value的说明
示例
1{ 2 "user/1**" : { 3 "4--60" : true, 4 "4--61" : "4.4.4.4" 5 }, 6 "user/2**" : { 7 "*" : true, 8 "4--60" : "4.4.4.4" 9 },10 "*" : {11 "*" : "8.8.8.8",12 "4--67" : true,13 }14}
说明:
json数组的第一级是url,注意这个url不带前导/,第二级是uid,匹配客户端请求参数中的userid;uid对应的值为Mock地址,如果值为true,则转到默认的...(Mock服务器)
这两级都可以配置为通配符*,匹配的优先级为先匹配精确的,再匹配通配符
例如上面这个例子中,如果4--61这个用户访问user/1**接口,则会转到4.4.4.4,而4--88这个用户访问的话,会被指到8.8.8.8
2、根据配置跳转业务请求到Mock服务
1-- 转到对应的机器 2local proxy_res = cache:get(project .. "_proxy") 3if proxy_res and proxy_res ~= "" and (ngx.req.get_headers()["x-proxy-host"] == nil or ngx.req.get_headers()["x-proxy-host"] == "") then 4 -- 增加错误处理,对于有问题的json啥的,做容错 5 local status, proxy_cnf = pcall(function(proxy_res) 6 local cjson = require "cjson" 7 return cjson.decode(proxy_res) 8 end 9 , proxy_res)10 if status == false then11 proxy_cnf = nil12 end1314 if proxy_cnf and type(proxy_cnf) == "table" then15 if proxy_cnf[ngx.var.arg_userid] then16 ngx.req.set_header("x-proxy-host", proxy_cnf[ngx.var.arg_userid])17 ngx.exec("@proxy")18 elseif proxy_cnf[ngx.var.arg_deviceid] then19 ngx.req.set_header("x-proxy-host", proxy_cnf[ngx.var.arg_deviceid])20 ngx.exec("@proxy")21 end22 end23end
使用方法
最后的最后,一切准备就绪,使用就非常简单了
使用方式一的步骤(如图):
1. 新增要Mock接口的uri对应的返回数据
2. Mock后台新增跳转配置,如配置user/1**, uid=4--86跳转
2. 手机/前端正常操做请求,即可返回定制数据
使用方式二的步骤:
1. wifi或本机hosts绑定请求接口域名到mock服务器 1.1.1.1 passport.**.com
2. 手机/前端连接绑host的wifi,返回定制数据
整个方案的过程中,我们也是一直在摸索调整,如:一开始的时候我们也并没有针对用户来做Mock数据的区分,使用时碰到开发有多人同时使用的情况,一个人改了数据,另外一个人使用时发现不对了;还有https证书的问题,花椒没有提供moco框架https服务需要的证书,为了Mock服务能同时支持客户端直接绑host的方式,采用ngnix跳转服务的方式,先处理完证书验证,再跳转到Mock服务。目前我们的Mock服务偏向于给开发人员和手工测试人员提供便捷的模拟服务,Mock在自动化测试上的应用还未被完全挖掘出来,有待进一步探讨。
界世的你当不
只做你的肩膀
无
360官方技术公众号
技术干货|一手资讯|精彩活动
空·
我知道你在看哟