Mock技术的使用
Mock是什么
- 在软件测试过程中,对一些不容易构造、获取的对象,用一个虚拟的对象来替代它,以达到相同的效果,这个虚拟的对象就是Mock。
- 在前后端分离项目中,当后端工程师还没有完成接口开发的时候,前端开发工程师利用Mock技术,自己用mock技术先调用一个虚拟的接口,模拟接口返回的数据,来完成前端页面的开发。
- 接口测试和前端开发有一个共同点,就是都需要用到后端工程师提供的接口。所以,当我们做接口测试的时候,如果后端某些接口还不成熟、所依赖的接口不稳定或者所依赖的接口为第三方接口、构造依赖的接口数据太复杂等问题时,我们可以用mock的方式先虚拟这些接口返回来代替,提高工作效率。
Mock的介绍
- 实现mock的技术很多,这些技术中,可以分为两类,mock数据和mock服务
- mock数据:即mock一个对象,写入一些预期的值,通过它进行自己想要的测试。常见有:EasyMock、Mockito、WireMock、JMockit,主要适用单元测试。
- mock服务:即mock一个sever,构造一个依赖的服务并给予他预期的服务返回值,适用范围广,更加适合集成测试,如:moco框架。
- Moco是类似一个Mock的工具框架,一个简单搭建模拟服务器的程序库/工具,下载就是一个JAR包,有如下特点
- 只需要简单的配置request、response等即可满足要求
- 支持http、https、socket协议,可以说是非常的灵活性
- 支持在request中设置Headers,Cookies,StatusCode等
- 对GET、POST、PUT、DELETE等请求方式都支持
- 无需环境配置,有Java环境即可
- 修改配置后,立刻生效。只需要维护接口,也就是契约即可
- 支持多种数据格式,如JSON、Text、XML、File等
- 可与其他工具集成,如Junit、Maven等,
Mock环境的搭建步骤
- 首先,下载mock的jar包地址:https://repo1.maven.org/maven2/com/github/dreamhead/moco-runner/1.1.0/moco-runner-1.1.0-standalone.jar
- 然后,创建一个json文件,如:test.json,用来模拟不同的请求、响应
- description是注释(描述),由于json无法写注释,所以提供了用这个key
- uri就是我们这个接口的统一资源标识符,可以根据模拟的接口自行定义
- reques、response里的内容分别为请求值、响应值
[ { "description": "demo", "request": { "uri": "/demo1" }, "response": { "text": "Hello,demo1" } } ]
- 接着,在有Java环境的机器上通过指定json文件启动mock服务,启动命令:java -jar moco-runner-1.1.0-standalone.jar http -p 8888 -c test.json
- 最后,请求接口查看返回数据,访问地址:http://ip:8888/demo1
Mock模拟不同的请求接口
1、约定GET请求方式接口并指定参数
[ { "description": "GET请求,并定义了请求参数,使用queries", "request": { "uri": "/request-get", "method": "GET", "queries": { "key1": "abc", "key2": "123" } }, "response": { "headers": { "Content-Type": "text/plain;charset=utf-8" }, "text": "Hello,这是第一个mock的Get请求接口!!!" } } ]
def mock_get() -> None: """ description: mock模拟get请求,需要使用已经定义的请求参数 :return: None """ # 构建请求url,请求参数 url = f"{MOCK_HOST}/request-get" param = { "key1": "abc", "key2": "123" } response = requests.get(url=url, params=param) print(response.text)
输出结果:Hello,这是第一个mock的Get请求接口!!!
2、约定POST请求方式接口并指定参数(表单格式:forms)
[ { "description": "POST请求,并定义请求参数form格式,使用forms", "request": { "uri": "/request-post", "method": "POST", "forms": { "key1": "ABC" } }, "response": { "headers": { "Content-Type": "text/plain;charset=utf-8" }, "text": "Hello,这是第一个mock的Post请求接口!!!" } } ]
def mock_post_forms() -> None: """ description: mock模拟post请求,使用自定义请求参数(表单格式) :return: None """ # 构建请求url,请求参数 url = f"{MOCK_HOST}/request-post" forms = { "key1": "ABC" } response = requests.post(url=url, data=forms) print(response.text)
输出结果:Hello,这是第一个mock的Post请求接口!!!
3、约定POST请求方式接口并指定参数(JSON格式:json)
[ { "description": "POST请求,并定义请求参数json格式,使用json", "request": { "uri": "/request-post-json", "method": "POST", "json": { "key1": "aaa" } }, "response": { "headers": { "Content-Type": "application/json;charset=utf-8" }, "status": 200, "json": { "key1": "aaa,post json!" } } } ]
def mock_post_json() -> None: """ description: mock模拟post请求,使用自定义请求参数(json格式) :return: None """ # 构建请求url,请求参数 url = f"{MOCK_HOST}/request-post-json" json = { "key1": "aaa" } response = requests.post(url=url, json=json) response.encoding = 'unicode_escape' print(response.json())
输出结果:{'key1': 'aaa,post json!'}
4、约定重定向接口
[ { "description": "模拟重定向接口", "request": { "method": "GET", "uri": "/redirect" }, "redirectTo": "https://www.baidu.com/" } ]
访问地址:http://ip:8888/redirect,会重定向到“https://www.baidu.com”
5、约定URI的“uri-startsWith”开始匹配
[ { "description": "约定uri-startsWith匹配", "request": { "uri": { "startsWith": "/sq" } }, "response": { "text": "Hello,uri-startWith!!!" } } ]
访问地址:http://ip:8888/sqxxx,都可以访问该请求
6、约定URI的“uri-endWith”结束匹配
[ { "description": "约定uri-endsWith匹配", "request": { "uri": { "endsWith": "sq" } }, "response": { "text": "Hello,uri-endWith!!!" } } ]
访问地址:http://ip:8888/xxxsq,都可以访问该请求
7、约定URI的“uri-contain”包含匹配
[ { "description": "约定uri-contain匹配", "request": { "uri": { "contain": "sq" } }, "response": { "text": "Hello,uri-contain!!!" } } ]
访问地址:http://ip:8888/xxsqxxx,都可以访问该请求
8.约定URI的返回状态码
[ { "description": "约定响应状态码", "request": { "uri": "/return_code" }, "response": { "status": 200 } } ]
访问地址:http://ip:8888/return_code,接口返回相应的状态码
Python请求Mock模拟的接口示例
- mock定义了请求的参数和返回值,如果请求的参不是预定的值,就无法请求成功
[ { "description": "支付接口", "request": { "headers": { "Content-Type": "application/json" }, "json": { "auth_code": "28763443825664394", "buyer_id": "2088202954065786", "out_trade_no": "20150320010101001", "seller_id": "2088102146225135", "subject": "Iphone6", "total_amount": "88.88" }, "method": "POST", "uri": "/trade/purchase" }, "response": { "headers": { "Content-Type": "application/json" }, "json": { "code": "40004", "msg": "Business Failed", "out_trade_no": "6823789339978248", "sub_code": "ACQ.TRADE_HAS_SUCCESS", "sub_msg": "交易已被支付", "trade_no": "2013112011001004330000121536" } } } ]
def mock_post_pay() -> None: """ description: mock模拟支付接口 :return: None """ # 构建请求url,请求参数 url = f"{MOCK_HOST}/trade/purchase" json = { "out_trade_no": "20150320010101001", "auth_code": "28763443825664394", "buyer_id": "2088202954065786", "seller_id": "2088102146225135", "subject": "Iphone6", "total_amount": "88.88" } response = requests.post(url=url, json=json) print(response.json())
输出结果:
{
"code": "40004",
"msg": "Business Failed",
"out_trade_no": "6823789339978248",
"sub_code": "ACQ.TRADE_HAS_SUCCESS",
"sub_msg": "交易已被支付",
"trade_no": "2013112011001004330000121536"
}