“AI 技术+人才”如何成为企业增长新引擎?戳此了解>>> 了解详情
写点什么

HTTPServerMock 从手工到平台的演变

  • 2015-10-21
  • 本文字数:8316 字

    阅读完需:约 27 分钟

不管是 Web 系统、还是移动 APP,各自在与内部、外部系统之间进行数据交互时,大多数情况下都是依赖接口。在基于接口约定开发的模式下,依赖接口的产出时间如果延迟,将直接影响了整个研发调试的效率;如果不能对接口进行及早测试,那发现问题的时间就要被推迟了。既然双方约定了接口格式,为何不按照这个规范直接测试,何必在乎依赖接口什么时候产出,优先做到及早自测,后续只要替换接口联调通过即可。本文主要讲解基于 HTTP 协议的 API 接口模拟,从手工 Mock 到平台的演变过程。

遇到的困惑

曾经遇到的困扰:在研发过程中接口调试对接难的问题:

场景一:

【需求阶段】新功能开发,Portal 依赖计费的接口,双方约定基于接口开发(内部、外部依赖接口场景均通用)

【开发阶段】Portal 在开发进行中,计费尚未开发完毕,Portal 迟迟不能与计费对接调试(也有可能版本迭代步伐不一致的情况),测试阶段一直被推迟;

另外,即使计费接口开发完毕,Portal 需要修改计费约定的接口数据进行调试,当发现没有对方接口权限或者计费没有过多人力资源来配合时,也无法进入更丰富的数据细节调试;

【测试阶段】测试人员无法及早介入到调试阶段进行接口测试,造成发现缺陷的最佳时期被推迟;

场景二:

【需求阶段】Portal 前、后端约定基于接口开发

【开发阶段】前端开发完毕,后端接口尚未开发完毕,前端只能硬编码数据进行测试,造成接口对接调试延后,而且每次进行更多场景的数据调试,需要频繁重启服务、本地部署;

研发自测阶段无法及早开展,依赖接口约束大。

场景三:

【需求阶段】移动 APP 项目依赖后端获取带宽数据的接口

【开发阶段】移动 APP 端通过后端系统 API 获取带宽数据,绘制带宽图,APP 端绘图工具开发完毕,后端 API 带宽接口尚未开发完毕,移动 APP 端只能硬编码数据进行测试,造成对接延后,每次进行更丰富的数据调试,需要频繁重启服务、本地部署;

研发自测阶段无法及早开展,依赖接口约束大。

总而言之,如图所示:

依赖接口开发完毕,才能够进入到接口联调测试阶段,即使 Portal 的功能开发已经完成,也无法进行自测联调,消耗的等待时间代价是不可估量的,效率低,。

图 -1- 传统的接口对接调试流程

野蛮的石器时代:手工作坊 -Nginx 反向代理

要解决在研发过程中接口对接调试难的问题,无非是所需即所有,减少等待时间,增加研发自测环节,同时也让测试及早参与进来,因此需要能够把依赖接口模拟出来(白盒方面的 Mock 有许多解决方案,这里主要讲的是基于 HTTP 请求的 API Server Mock),以便提高生产效率,改进流程如图所示:

图 -2- 改进的接口对接调试流程

当前最简单的想法是要解决:基于 HTTP 请求、固定 url、能够正则匹配,在这个需求的驱动下,通过 Nginx 的反向代理能够解决问题。

匹配具体路径下某 html 文件

复制代码
location ~ ^/live/(.*)\.html$ {
root /home/htmlfile/ms;
}
location ~ ^/live/([A-Z0-9]+)$ {
}

定义具体返回码

复制代码
location ~ ^/schedule/.*\.(json)$ {
error_page 404 /404.html;
}

请求 http://info.schedul.com/schedule/1234.json 返回 404。

定义其它状态码也是同样道理:

复制代码
error_page 403 /error/403.html;
error_page 500 501 502 503 504 /error/500.html;

俗话说:术业有专攻,Nginx 并不擅长做 Mock API 的工具,在管理配置文件即使可以通过 svn 进行管理,依然是维护比较困难,对于不熟悉 Nginx 的测试工程师,也有一定的学习成本。

拿来主义:不重复造轮子- 开源WireMock

经历了Nginx 的配置繁琐,决定另寻新路,有开源的WireMock( http://wiremock.org/):

Ø WireMock 是一个灵活的库,用于 Web 服务测试,和其他测试工具不同的是:WireMock 创建一个实际的 HTTP 服务器来运行你的 Web 服务以方便测试;

Ø 支持 HTTP 响应存根、请求验证、代理 / 拦截、记录和回放;

创建一个基于 WireMock 的 JavaProject(运行在 tomcat 下管理):

图 -3-ServerMock Project

web.xml 配置如下:

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_9"
version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<listener>
<display-name>wiremock-startup-listener</display-name>
<listener-class>com.github.tomakehurst.wiremock.servlet.
WireMockWebContextListener</listener-class>
<description>Loads WireMock and populates the servlet
context with its services</description>
</listener>
<context-param>
<param-name>WireMockFileSourceRoot</param-name>
<param-value>/WEB-INF/wiremock</param-value>
</context-param>
<context-param>
<param-name>verboseLoggingEnabled</param-name>
<param-value>false</param-value>
</context-param>
<servlet>
<servlet-name>wiremock-mock-service-handler-servlet</servlet-name>
<servlet-class>com.github.tomakehurst.wiremock.jetty6.
Jetty6HandlerDispatchingServlet</servlet-class>
<init-param>
<param-name>RequestHandlerClass</param-name>
<param-value>com.github.tomakehurst.wiremock.http.
StubRequestHandler</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>wiremock-mock-service-handler-servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>wiremock-admin-handler-servlet</servlet-name>
<servlet-class>com.github.tomakehurst.wiremock.jetty6.
Jetty6HandlerDispatchingServlet</servlet-class>
<init-param>
<param-name>RequestHandlerClass</param-name>
<param-value>com.github.tomakehurst.wiremock.http.
AdminRequestHandler</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>wiremock-admin-handler-servlet</servlet-name>
<url-pattern>/__admin/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.json</welcome-file>
<welcome-file>index.xml</welcome-file>
<welcome-file>index.html</welcome-file>
<welcome-file>index.txt</welcome-file>
</welcome-file-list>
<mime-mapping>
<extension>json</extension>
<mime-type>application/json</mime-type>
</mime-mapping>
<mime-mapping>
<extension>xml</extension>
<mime-type>application/xml</mime-type>
</mime-mapping>
<mime-mapping>
<extension>html</extension>
<mime-type>text/html</mime-type>
</mime-mapping>
<mime-mapping>
<extension>txt</extension>
<mime-type>text/plain</mime-type>
</mime-mapping>
</web-app>

web.xml 的这项配置可以改变源文件位置

复制代码
<context-param>
<param-name>WireMockFileSourceRoot</param-name>
<param-value>/WEB-INF/wiremock</param-value>
</context-param>

使用 Maven 管理依赖,配置如下:

复制代码
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>1.53</version>
<!-- Include everything below here if you have dependency conflicts -->
<classifier>standalone</classifier>
<exclusions>
<exclusion>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</exclusion>
<exclusion>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>
</exclusion>
<exclusion>
<groupId>xmlunit</groupId>
<artifactId>xmlunit</artifactId>
</exclusion>
<exclusion>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
</exclusion>
<exclusion>
<groupId>net.sf.jopt-simple</groupId>
<artifactId>jopt-simple</artifactId>
</exclusion>
</exclusions></dependency>

具体的部署这里就不介绍了,说说 WireMock 的配置:

Ø WireMock 的文件目录

如图所示:

mappings: 存放映射描述的文件

__files: 存放映射匹配结果的文件

图 -4-WireMock 的文件目录

WireMock 的匹配规则示例

分两种:完整 Url 匹配和正则 UrlPattern

Url:完全匹配

mappings:cities-mapping.json

复制代码
{
"request": {
"method": "GET",
"url": "/cities"
},
"response": {
"status": 200,
"bodyFileName": "/cities.json",
"headers": {
"Content-Type": "application/json",
"Cache-Control": "max-age=86400"
}
}
}

__files:cities.json

复制代码
{
"cityName": " 公司操作间 ",
"shortname": "WS",
"provinceName": " 北京 ",
"provinceNameEn": "BeiJing City",
"code": "0001",
"cityNameEn": "Workshop"
}

UrlPattern:正则匹配任何 6 位数的,例如:/customer/123456/

mappings:cities-mapping.json

复制代码
{
"request": {
"method": "GET",
"urlPattern": "/customer/[0-9]{6}/"
},
"response": {
"status": 200,
"bodyFileName": "/customer.json",
"headers": {
"Content-Type": "application/json",
"Cache-Control": "max-age=86400"
}
}
}

__files:customer.json

复制代码
{
"channels": [],
"code": "781",
"companyName": "",
"enable": true,
"name": "163",
"password": "CC@ne.com",
"userState": "COMMERCIAL"
}

自给自足:平台化

使用 WireMock 通过 mappings 和 __files 文件夹可以有效管理映射和返回内容文件,但是所有文件的有部分可抽取未固定模板,而这些部分目前是手动编辑,关注这些部分会分散业务的精力,如果可以做成平台化管理,所有接口通过创建完成,文件命名规则全部由系统进行管理,将节省的时间更多投入业务关注和及早进行自测,这样子的收益将会更大。

那怎么样的平台才算能够满足当前需求呢?

  • 基于 HTTP 协议
  • 支持 Url、UrlPattern 匹配
  • 支持数据存储
  • API 接口规范化管理
  • 提交表单即可生成 mapping 和 __files 所需文件
  • 不同项目接口有不同的前缀
  • 能够返回指定格式(json|xml|文本)内容

图 -4-ServerMock-v1.0- 架构图

根据架构图,做了总体规划如下:

(点击放大图像)

图-5-ServerMock-v1.0 规划

技术选型

由于原来的测试平台使用Python 编写,为了保持风格一致,从界面录入到文件生成处理依然采用Python,后台工具使用WireMock 的standalone 模式,通过shell 脚本进行一键启停管理,以及实时刷新url、mapping 映射;

HTTP API Mock 项目管理 Web 前台

使用 Python+Django+MySQL 进行开发,分为项目配置和接口配置两大部分。

项目配置页

介绍:配置协议、进行 mock 服务器的重启、重新加载(有新的接口文件生成系统会自动 reset 即可,当然手工 reset 也可以,即时加载无须重启服务等待)。

图 -6- 项目配置页

接口列表页

介绍:展示列表,列出相关 URL、方法、是否正则、返回码、返回类型。

图 -7- 接口列表页

接口配置页

介绍:选择方法、URL 类型,填写 URL(如果选择 URL 类型为 UrlPattern,则填写正则表达式),填写状态码、返回接口,以及返回头,就可以完成一个 mock 接口的创建。

图 -8- 接口配置页

接口配置有三种输入形式:

直接输入返回结果

(点击放大图像)

图-9- 手工输入

一般场景在返回结果500k 以内的内容,可以直接输入,保存进入数据库;

通过url 抓取返回结果

图-10-url 抓取

一般场景在返回结果超过500k 以上内容,目标Mock 接口已经存在,可以直接抓取生成文件;

通过文件上传返回结果

图-11- 上传文件

一般场景在返回结果比较大|目标Mock 接口还未开发完成,手工上传返回内容的文件即可。

以上三种灵活的保存返回内容方式,最终保存的接口会按照以下格式生成mapping 和__files 所需文件:

图-12-mapping 和__files 文件格式

Mock 项目管理 Server 后台

使用 Java-WireMock 进行后台服务,在项目配置页通过按钮:重启、重新加载,调用后台脚本:wiremock_controller.sh,仅供参考:

复制代码
#!/bin/bash
if [ "$#" = 0 ];then
echo "Usage: $0 (start|stop|restart|reset)"
exit 1
fi
dirWiremock=`pwd`
getCount=`ps -ef | grep "wiremock-1.53-standalone" | grep -v "grep" |wc -l`
wiremock_jar=${dirWiremock}/wiremock-1.53-standalone.jar
port=9999
wiremock_url=http://localhost:${port}
stop(){
count=${getCount}
if [ 1==${count} ];then
curl -d log=aaa ${wiremock_url}/__admin/shutdown
echo "Stop success!......"
else
echo "Already stop"
fi
}
start(){
count=${getCount}
if [ 0==${count} ];then
nohup java -jar ${wiremock_jar} --verbose=true --port=${port} &
echo "Start success!......"
else
echo "Already start"
fi
}
if [ "$1" = "restart" ];then
count=${getCount}
if [ 1==${count} ];then
echo "Wiremock is running,wait for restarting! ...."
stop
echo "Start wiremock......"
start
else
start
fi
elif [ "$1" = "start" ];then
echo "Start wiremock......"
start
elif [ "$1" = "stop" ];then
echo "Stop wiremock......"
stop
elif [ "$1" = "reset" ];then
count=${getCount}
if [ 0==${count} ];then
echo "Wiremock must be running before reset,wait for starting! ...."
start
fi
curl -d log=aaa ${wiremock_url}/__admin/mappings/reset
echo "Reset success!......"
fi

其中:

“nohup java -jar ${wiremock_jar} --verbose=true --port=${port} &”:在 linux 系统后台运行 WireMock;

“curl -d log=aaa ${wiremock_url}/__admin/mappings/reset”:是通过发送 POST 请求,重新加载新生成的配置文件,在 WireMock 的源码中可以看到:reset 的作用:

复制代码
public interface Admin {
void addStubMapping(StubMapping stubMapping);
ListStubMappingsResult listAllStubMappings();
void saveMappings();
void resetMappings();
void resetScenarios();
void resetToDefaultMappings();
VerificationResult countRequestsMatching(RequestPattern requestPattern);
FindRequestsResult findRequestsMatching(RequestPattern requestPattern);
void updateGlobalSettings(GlobalSettings settings);
void addSocketAcceptDelay(RequestDelaySpec spec);
void shutdownServer();
}

通过一系列源码追溯,可以找到重置:

复制代码
@Override
public void reset() {
mappings.clear();
scenarioMap.clear();
}

可以推测映射文件是存放到列表的:

复制代码
public class SortedConcurrentMappingSet implements Iterable<StubMapping>{
private AtomicLong insertionCount;
private ConcurrentSkipListSet<StubMapping> mappingSet;
......
}

当 WireMock 启动,日志有以下描述:

复制代码
2015-02-12 11:38:37.844 Verbose logging enabled
2015-02-12 11:38:38.657:INFO::Logging to STDERR via wiremock.org.mortbay.log.StdErrLog
2015-02-12 11:38:38.664 Verbose logging enabled
/$$ /$$ /$$ /$$ /$$ /$$
| $$ /$ | $$|__/ | $$$ /$$$ | $$
| $$ /$$$| $$ /$$ /$$$$$$ /$$$$$$ | $$$$ /$$$$ /$$$$$$ /$$$$$$$| $$ /$$
| $$/$$ $$ $$| $$ /$$__ $$ /$$__ $$| $$ $$/$$ $$ /$$__ $$ /$$_____/| $$ /$$/
| $$$$_ $$$$| $$| $$ \__/| $$$$$$$$| $$ $$$| $$| $$ \ $$| $$ | $$$$$$/
| $$$/ \ $$$| $$| $$ | $$_____/| $$\ $ | $$| $$ | $$| $$ | $$_ $$
| $$/ \ $$| $$| $$ | $$$$$$$| $$ \/ | $$| $$$$$$/| $$$$$$$| $$ \ $$
|__/ \__/|__/|__/ \_______/|__/ |__/ \______/ \_______/|__/ \__/
port: 9999
enable-browser-proxying: false
no-request-journal: false
verbose: true

图 -13-WireMock 启动

成功处理请求的日志:

复制代码
2015-02-12 11:41:10.320 Received request: GET /test/today/dkfDF123/1234/ HTTP/1.1
Host: 192.168.32.55:9999
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:33.0) Gecko/20100101 Firefox/33.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: csrftoken=alXbvCtMyTBI1wnSnRoljguTaBnTDbPo; sessionid=tvoi9rzs66umnt1a26wsj36eqry2e2lo
Connection: keep-alive

总结

HTTP API 接口测试痛点是什么?很多公司划分不同研发组,各组系统之间的数据交互通过接口来实现,那很多时候就是集中在接口开发不同步,测试无法及早参与,对接调试难的问题。或许很多团队遇到这种问题,就是选择同步开发或者等待。当你选择等待的时候,你的产品质量就得不到及时验证,因为根本没有测试过,在当前快速迭代的开发模式中,时间是最致命的要素,如果不能及时交付,交付的质量又得不到保证,那是相当被动的局面,最后返工的成本比你当时愿意追加测试的成本会来的更高。

遇到这类问题是想办法解决,而不是回避,我们可以借鉴《自动化单元测试实践之路》在单元测试中,使用Mockito 对依赖进行Mock,那同样道理,使用Mock 技术也可以对HTTP API 进行Mock,按照这个思路探索下去,看看有没有开源解决方案,是否能够解决当前问题,如果可以就不用重复写一套解决方案;如果不行,那能否基于开源的做二次开发呢?当团队经历过测试痛点,调研收集了一定的数据,这些问题的答案就会浮出水面了。

或许有人要问,使用之后能够提高多少效率呢?看回《图-2- 改进的接口对接调试流程》,根据我们的经验,要统计当前迭代中有多少API 需要对接调试,如果对比旧的模式来说,API 接口调试效率提升至少有10%;可想而知,迭代中全是依赖API 接口开发的话,那提升的效率就相当可贵了。

作者简介

李乐, 测试经理,8 年以上工作经验,目前就职于ChinaCache 质量部, 博客:jooben.blog.51cto.com, 微博:weibo.com/iamlile


感谢郭蕾对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群)。

2015-10-21 11:014181

评论

发布
暂无评论
发现更多内容

利用 Dio 完成数据更新的 Patch 请求

岛上码农

flutter 安卓开发 4月月更 跨平台开发 ios 开发

【漏洞分析】jdk9+Spring及其衍生框架

网络安全学海

网络安全 信息安全 渗透测试 WEB安全 漏洞挖掘

软件激活码是什么?

InfoQ IT百科

云原生新时代弄潮儿k8s凭什么在容器化方面独树一帜?

囧么肥事

Kubernetes 容器 k8s 容器服务 Kubernetes 集群

极致体验,揭秘抖音背后的音视频技术

火山引擎边缘云

音视频 边缘计算 音视频技术

外包学生管理系统--架构详细设计方案

凯博无线

残酷春天里的中国科技(四):跨越地方保护主义

脑极体

浅析分布式系统之体系结构 技术基本目标----一致性(单对象、单操作)

snlfsnef

分布式 系统设计 基本原则 一致性 设计思想

怒肝 JavaScript 数据结构 — 队列篇

杨成功

数据结构 4月月更

怒肝 JavaScript 数据结构 — 双端队列篇

杨成功

数据结构 4月月更

有哪些比较靠谱的低代码开发平台?

InfoQ IT百科

做 App 还是小程序好?

InfoQ IT百科

zip格式的文件怎么打开?

InfoQ IT百科

SOFARegistry 源码|数据分片之核心-路由表 SlotTable 剖析

SOFAStack

GitHub 开源 程序员 开发者 源码解析

DAO社区的胜利,Tiger DAO VC胜在治理与共识

西柚子

DAO社区的胜利,Tiger DAO VC胜在治理与共识

小哈区块

使用WPS需要注册/登录账号吗?

InfoQ IT百科

架构实战营作业三

热猫

基于DDD思想的技术架构战略调整

Qunar技术沙龙

DDD 构架

linux之fping命令

入门小站

Linux

在线CSV转XML/JSON工具

入门小站

工具

怒肝 JavaScript 数据结构 — 队列实战篇

杨成功

数据结构 4月月更

低代码开发平台能开发哪些应用?

InfoQ IT百科

安卓App和鸿蒙App有什么不同?

InfoQ IT百科

在线YAML转HTML工具

入门小站

工具

未来几年如何把握住音视频开发的大浪潮,音视频高级开发工程师培养计划

赖猫

音视频 编程开发 音视频开发

关于缓存更新的一些可借鉴套路

架构精进之路

缓存 4月日更 4月月更

现在企业开发哪种APP有前景?

源字节1号

微信小程序 软件开发 前端开发 后端开发

趁着同事玩游戏偷偷认识k8s一家子补补课

囧么肥事

Kubernetes 容器 云原生 k8s Kubernetes 集群

appdata是什么文件夹?

InfoQ IT百科

如何隐藏电脑上的文件?

InfoQ IT百科

HTTPServerMock从手工到平台的演变_软件工程_乐少_InfoQ精选文章