标签:
浅谈WireMock结合Mock+Proxy应用于异常测试
最近在做NCE自动化接口测试,按照尽可能覆盖逻辑的原则,写了200+用例,但实际去实现的时候,大概能做的就100不到,完成的大多是能通过传入参数去控制系统的逻辑走向和结果的case,即传入指定的参数,调用发送请求工具,对结果进行校验。那么除此之外,哪些是难以实现的呢?
从被测系统本身来说,NCE是建立在底层服务之上的一套系统,他的主要职能还是资源分配和调度,比如调云主机的接口拿两台机器,又去申请几块硬盘挂上,然后还要在机器上部署k8s的组件,调k8s创建rc和pod等,考虑到调用以异步为主,还要加上轮询,所以大量的调用外部的接口,以及结果判断的逻辑。而调用外部服务,想要获得预期的结果,并不是那么容易,需要额外了解什么情况下才会出现异常,有些情况难以模拟又不能跑去把公共的服务搞坏。这不仅仅是自动化容易遇到的问题,手工测试也同样会遇到。
这种上层服务测试所遇到的问题一般是怎么解决的呢。就之前的经验来说,需要借助mock来实现,比如之前在微信支付,后台依赖的是财付通的服务,很多异常需要由财付通后台返回,另外由于测试环境是对接的,财付通做异常测试或是环境出现问题的时候,微信支付的正常的测试活动就不能自理了,所以需要mock来降低对外部环境的依赖。仅有mock还是不够,如果一个人mock了一个服务,那么所有人都走到了被mock的服务上,如果被mock的返回是异常,那么所有人的测试活动又没有办法进行,如果被mock返回的是成功,对别人又是一种误导。所以最后采用的是一种mock+选择的模式,一般以帐号+接口名为维度来控制请求访问指定的目的地,如图
如果要对NCE系统做异常测试,mock是比较容易找的,由于协议都是http协议而不是什么自封装的协议,所以选择比较多,其中WireMock是一种较好的选择,之前有同学已经推荐过了WireMock——轻量级HTTP Mock服务器),自己尝试过一下,也够简单易用。分发方面,可以考虑python的HTTPServer做一个转发服务器,收到请求后,解析请求串转发给想要的url,然后把请求返回回去
但还要加上规则匹配什么的就觉得心好累,后来发现WireMock自己就支持实时设定这种更为简单的方式,就转为全都依赖WireMock
目前的服务访问外部的url都是在配置文件中,如果每次都要改url配置指向mock,用mock测完再改回来无疑是一种低效的做法,结合WireMock的proxy功能希望能对所有可能的外部访问进行管理
使用WireMock proxy前的容器服务(举例)
使用WireMock proxy后,我们所期望的
1.下载并启动WireMock-xx-standalone.jar
下载的WireMock的standalone,放到某台主机上
基本的启动方式:
# java -jar wiremock-1.55-standalone.jar --verbose
java -jar wiremock-1.55-standalone.jar –help 有一些可以使用的命令,加–verbose打开verbose信息输出到屏幕方便调试 ,当服务器使用可以按如下命令挂在后台
# nuhup java -jar wiremock-1.55-standalone.jar &
欢迎页就是这个鸟样:
然后将修改配置需要代理的链接切到WireMock,由上图看出WireMock默认端口8080,当然可以自己用–port指定。比如需要将kube的url从10.180.155.13:8080变为10.180.148.30:8080(WireMock端口)
启动过WireMock后目录下面有2个文件夹:mapping和__file。mapping里面是收发的规则配置,mapping里面指定了返回的文件名的话,就会从__file里面把文件给取出来,若规则匹配,则会返回对应的内容(预定的字符串或文件内容或网络错误),不匹配则返回404not found。
这里要使用proxy模式,请求通过wiremock进行代理,在mapping里面配置proxy规则,proxy的配置和mock配置类似,简单配置如下
新建container-mapping.json文件,加入一下配置
{ "request": { "method": "GET", "urlPattern": "/api/v1beta2/.*" }, "response": { "proxyBaseUrl" : "http://10.180.155.13:8080/" } }
于是/api/v1beta2的http请求就从container->wiremock->kube,体验上却和直接container->kube一致
WireMock日志
2015-05-30 12:09:48.36 Request received: GET /api/v1beta2/pods?labels=podlabel%3Dapitest-d5e3e7cf9bee471f8ff642d1258e1354-go00o HTTP/1.1 User-Agent: curl/7.26.0 Host: 10.180.148.30:8080 Accept: */* 2015-05-30 12:09:48.76 Proxying: GET http://10.180.155.13:8080//api/v1beta2/pods?labels=podlabel%3Dapitest-d5e3e7cf9bee471f8ff642d1258e1354-go00o
其他http链接也可以用这种方式配置好
现在已经让外部链接纳入WireMock的控制了,如果想做网络异常或指定返回,可以添加另一个规则,将这个url对应的请求都返回指定值,但必然只有一个生效,比如
{ "request": { "method": "GET", "urlPattern": "/api/v1beta2/.*" }, "response": { "proxyBaseUrl" : "http://10.180.155.13:8080/" } } { "request": { "method": "GET", "urlPattern": "/api/v1beta2/.*" }, "response": { "status": 200, "body": "mocked by hzmali!\n" } }
实际情况下,谁在前面就优先使用那个规则,是否要每次做完异常测试需要再改回配置到正常方式呢,如果是手工测试还可以接受,如果是自动化测试,那么就会很麻烦.幸运的是,WireMock 提供了通过__admin/mappings/new接口来远程配置规则的功能。
尝试在mapping下创建了一个test.json
{ "request": { "url": "/get/this", "method": "GET" }, "response": { "status": 200, "body": "on disk!\n" }}
重启stansdalone,发现规则生效
curl "http://10.180.148.30:8080/get/this" on disk!
调用__admin/mappings/new配置/get/this返回NO
# curl -X POST --data ‘{ "request": { "url": "/get/this", "method": "GET" }, "response": { "status": 200, "body": "NO!\n" }}‘ http://10.180.148.30:8080/__admin/mappings/new # curl "http://10.180.148.30:8080/get/this" NO!
再配置/get/this返回YES
# curl -X POST --data ‘{ "request": { "url": "/get/this", "method": "GET" }, "response": { "status": 200, "body": "YES!\n" }}‘ http://10.180.148.30:8080/__admin/mappings/new # curl "http://10.180.148.30:8080/get/this" YES!
重启standalone,之前配置的结果不存在,恢复为配置文件的结果:
#curl "http://10.180.148.30:8080/get/this" on disk!
各位看官大概可以猜想到,stanalone启动时加载了本地的mapping配置到内存,/__admin/mappings/new这个接口将发送过去的规则置为最新规则,根据官方文档的说法,最新的规则会生效:
By default, WireMock will use the most recently added matching stub to satisfy the request. However, in some cases it is useful to exert more control.(exert more control 指的是使用”priority”: 1,这样的字段来指定优先级来破坏最近匹配的规则,目前没有怎么使用)
WireMock standalone能通过接口设置规则,以及最新生效这2个特性,对自动化用例绝对是一个喜大普奔的消息,在模拟外部资源返回特定消息或异常时,可以采取这样通用的方式:
1.正常case:外部访问都通过WireMock的proxy,映射关系都写在本地配置,WireMock启动就自动加载,不影响正常功能,上层无感知
2.需要mock的case:执行前将想要的返回以及匹配规则发送到/__admin/mappings/new接口使之生效,执行完成后重置规则使用proxy方式
简单的tesng case写法,通过设置规则控制返回是proxy的结果还是自己所期望的mock返回
/*WebUserParameterTestData.class的DataProvider*/ @DataProvider(name = "sample") public static Object[][] sample(){ String jsonString="{\"request\": {\"method\": \"GET\",\"urlPattern\": \"/api/v1beta2/pods.*\"},\"response\": {\"status\": 200,\"body\": \"mocked by hzmali\"}}"; return new Object[][]{ {jsonString, "and"}, }; } /*case主要部分*/ @BeforeClass @Parameters({ "env" }) public void init(String env) throws IOException, InterruptedException { CommonData.init(env); nce_conn = new NceHttpBiz(CommonData.WEBHOST); mock_conn=new MockTool(); //打印当前结果 log.info("[befor test]response: "+nce_conn.getPods()); //设置规则放到test里做 } @AfterMethod public void recoverMapping() throws IOException{ //恢复规则 String rule="{\"request\": {\"method\": \"GET\",\"urlPattern\": \"/api/v1beta2/pods.*\"},\"response\": { \"proxyBaseUrl\" : \"http://10.180.155.13:8080/\"}}";; mock_conn.setMapping(rule); //打印恢复后的结果 log.info("[after test]response: "+nce_conn.getPods()); } @Test(dataProvider = "sample", dataProviderClass =WebUserParameterTestData.class) public void sample(String rule,String expeted) throws IOException { System.out.println(rule); //设置期望的返回 mock_conn.setMapping(rule); //打印预期的返回 log.info("[testing]response: "+nce_conn.getPods()); }
打印的结果表示符合预期
[INFO ]17:32:34, [Class]NceHttpBiz, [Method]getPods, ==========call getPods============= [INFO ]17:32:34, [Class]MockCase, [Method]init, [befor test]response: { "kind": "Status", "creationTimestamp": null, "apiVersion": "v1beta2", "status": "Failure", "message": "invalid selector: ‘podlabel%3Dapitest-d5e3e7cf9bee471f8ff642d1258e1354-go00o‘; can‘t understand ‘podlabel%3Dapitest-d5e3e7cf9bee471f8ff642d1258e1354-go00o‘", "code": 500 } [INFO ]17:32:34, [Class]MockTool, [Method]setMapping, ==========setMapping============= {"request": {"method": "GET","urlPattern": "/api/v1beta2/pods.*"},"response": {"status": 200,"body": "mocked by hzmali"}} [INFO ]17:32:34, [Class]NceHttpBiz, [Method]getPods, ==========call getPods============= [INFO ]17:32:34, [Class]MockCase, [Method]sample, [testing]response: mocked by hzmali [INFO ]17:32:34, [Class]MockTool, [Method]setMapping, ==========setMapping============= [INFO ]17:32:34, [Class]NceHttpBiz, [Method]getPods, ==========call getThis============= [INFO ]17:32:34, [Class]MockCase, [Method]recoverMapping, [after test]response: { "kind": "Status", "creationTimestamp": null, "apiVersion": "v1beta2", "status": "Failure", "message": "invalid selector: ‘podlabel%3Dapitest-d5e3e7cf9bee471f8ff642d1258e1354-go00o‘; can‘t understand ‘podlabel%3Dapitest-d5e3e7cf9bee471f8ff642d1258e1354-go00o‘", "code": 500 }
实际使用中,比如测试部署服务时,pod状态不正确的场景,需要把查询pod这个接口mock掉,kube其他接口都走proxy方式,实际用例会复杂一些
WireMock的使用上也有一些不方便的地方:由于是proxy,所以如果有同名接口,需要使用额外的正则方式或是新的WireMock进程;目前期望的返回都是由用例来控制,可控性强,对于一些大粒度的异常场景模拟较好,如泛失败,网络错误等,但精确模拟各种场景下正确的返回则需要花一点功夫,构造符合数据相关性的返回也是一个挑战。目前只是初步使用,还有很多坑没踩到,乐观的来看这种方式能够解决一些问题,而且代价不高,可以拿来先顶顶。
标签:
原文地址:http://www.cnblogs.com/opama/p/4574821.html