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