HTTP Server Mock 从手工到平台的演变(二)
大家都知道,不管是 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; }
定义其它状态码也是同样道理:
error_page 403 /error/403.html; error_page 500 501 502 503 504 /error/500.html;</pre>
俗话说:术业有专攻,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> //如果对软件测试、接口测试、自动化测试、性能测试、LR脚本开发、面试经验交流。 <context-param> //感兴趣可以175317069,群内会有不定期的发放免费的资料链接,这些资料 <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 规划
如果对软件测试、接口测试、自动化测试、性能测试、LR脚本开发、面试经验交流。感兴趣可以175317069,群内会有不定期的发放免费的资料链接,这些资料都是从各个技术网站搜集、整理出来的,如果你有好的学习资料可以私聊发我,我会注明出处之后分享给大家。
技术选型
由于原来的测试平台使用 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 {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
如果对软件测试、接口测试、自动化测试、性能测试、LR脚本开发、面试经验交流。感兴趣可以175317069,群内会有不定期的发放免费的资料链接,这些资料都是从各个技术网站搜集、整理出来的,如果你有好的学习资料可以私聊发我,我会注明出处之后分享给大家。
图 -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 接口开发的话,那提升的效率就相当可贵了。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
如何使用Meteor开发以太坊Dapp
本教程将向你展示如何设置Meteor应用程序以用作Ðapp,并可能回答几个关于为什么应该使用Meteor的问题。 常问问题 Meteor不是一个完整的堆栈框架,它是如何适应Ðapp开发的 是的,Meteor是一个完整的堆栈框架,它的主要改进是实时Web应用程序,但Meteor也是第一个框架(我知道),它完全支持了单页app(SPA)开发并提供了所有必要的工具。 Meteor非常适合的5个理由: 它纯粹用JS编写,具有SPA所需的所有工具(模板引擎,模型,动态编译,绑定等)。 你将获得一个开发环境,它具有实时重新加载,CSS注入和支持许多预编译器(LESS,Coffeescript等)即插即用。 你可以使用meteor-build-client将所有前端代码作为单个index.html,使用一个js和css文件加载你的资源。然后,你可以在任何地方托管它或简单地运行index.html本身或稍后在swarm上分发它。 它包含完整的响应性,这使得构建一致的界面更容易(类似于angular.js $scope或binding) 它有一个名为Minimongo的优秀模型,它为你提供了一个类似mo...
- 下一篇
针对innodb_flush_method参数的理解和对比测试(mycat+mysql)
mysql的innodb_flush_method这个参数控制着innodb数据文件及redo log的打开、刷写模式,对于这个参数,文档上是这样描述的: 有三个值:fdatasync(默认),O_DSYNC,O_DIRECT 默认是fdatasync,调用fsync()去刷数据文件与redo log的buffer 为O_DSYNC时,innodb会使用O_SYNC方式打开和刷写redo log,使用fsync()刷写数据文件 为O_DIRECT时,innodb使用O_DIRECT打开数据文件,使用fsync()刷写数据文件跟redo log 首先文件的写操作包括三步:open,write,flush 上面最常提到的fsync(int fd)函数,该函数作用是flush时将与fd文件描述符所指文件有关的buffer刷写到磁盘,并且flush完元数据信息(比如修改日期、创建日期等)才算flush成功。 使用O_DSYNC方式打开redo文件表示当write日志时,数据都write到磁盘,并且元数据也需要更新,才返回成功。 O_DIRECT则表示我们的write操作是从MySQL inno...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- CentOS8编译安装MySQL8.0.19
- Red5直播服务器,属于Java语言的直播服务器
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS7设置SWAP分区,小内存服务器的救世主
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库