在测试后端的服务器程序时,经常会遇到测试 A 程序的接口时,A 程序要通过 http 协议访问 B 程序。B 程序返回的数据不一样时,A 程序执行的代码逻辑不一样。而且有些场景,B 程序正常情况下是不容易出现的。比如 http 响应超时,响应非法的数据。所以需要一种方法,可以方便的模拟 B 程序的各种不同的响应。解决依赖服务的方法很多。比如用 nginx 搭建一个静态的 http 服务器。还有其它类似的工具。它门的使用方式大多数都是启动一个进程,然后指定一个模拟响应数据的配置文件
不方便动态改变响应数据,比如第 1 个用例需要依赖服务响应的状态码是 200,第 2 个用例需要依赖服务响应的状态码是 500。如果要在自动化代码里完成这些,需要在用例 1 里去动态修改配置文件返回 200。在用例 2 里再次去修改配置文件返回 500,这就比较麻烦了。
不方便查询模拟服务接收到的请求数据。比如说 A 程序有个功能是通过 http 协议调 B 程序的鉴权接口。现在需要验收 A 程序请求 B 程序接口的所有请求数据,是否全部都符合接口文档。如果是手动测试的话,直接抓包看就可以了。但是要在自动化测试里完成这个用例,就不好用抓包了。当然如果用 nginx 来做模拟服务,也可以在自动化代码里抓取 nginx 的日志回来,再解析日志来断言请求数据是否正确。然而这不够方便
为了达到这个目的,写了这个 auto-mock-server。已上传到 pypi
运行环境:需要安装 python3,在 windox 或者 linux 系统运行
先安装 auto-mock-server,直接 pip install auto-mock-server -i https://pypi.douban.com/simple
from mock import MockServerClass
import requests
def test_basic_functions_of_the_mock_server():
#mock_server_port 是模拟服务的监听端口
mockserver = MockServerClass(mock_server_port=82)
mockserver.add_route(route_path="/login", response_status='200', response_headers='{"Test_key":"Test_value"}',
response_date='{"Whether login succeeded":true}',processing_time="2")
"""
add_route 方法的参数说明
:param route_path: 字符串类型, 模拟接口的请求路径, 如果值为mock_other_path,请求url和其它接口不匹配时,会用此接口响应
:param response_status: 字符串类型,指定响应状态码
:param response_headers: 字符串类型,字符串里的内容是一个字典 比如 '{"test_key":"test_value"}',指定模拟服务器的响应头
:param response_date: 字符串类型,模拟接口的http body响应数据
:param processing_time: 字符串类型,指定模拟服务特意 sleep固定的时间,再返回数据,默认值是0,单位是秒
:return:
"""
#调用start_mock_server()方法后,才会真正的启动模拟服务
mockserver.start_mock_server()
response = requests.get("http://127.0.0.1:82/login")
#断言模拟模拟服务响应的状态码是add_route函数设置的值
assert response.status_code == 200
#断言模拟服务的响应头有add_route设置的值
assert response.headers["Test_key"] == "Test_value"
#断言模拟服务的响应body是add_route设置的值
assert response.text == '{"Whether login succeeded":true}'
#断言模拟服务器会等待processing_time秒的时间,再返回数据
assert response.elapsed.total_seconds() >= 2
可以在一个模拟服务配置多个不同路径的接口,即可以实现根据不同的请求路径响应不同的数据
from mock import MockServerClass
import requests
def test_add_multiple_routes():
mockserver = MockServerClass(mock_server_port=82)
mockserver.add_route(route_path="/login", response_status='200', response_headers='{"test_key":"test_value"}',
response_date='{"Whether login succeeded":true}')
mockserver.add_route(route_path="/login2", response_status='200', response_headers='{"test_key":"test_value"}',
response_date='{"Whether login succeeded":false}')
mockserver.add_route(route_path="/mock_other_path", response_status='200',
response_date='other_path_content')
mockserver.start_mock_server()
# 这样当在本机请求 http://127.0.0.1:82/login 的时候,模拟服务就会响应 {"Whether login succeeded":true}
response = requests.get("http://127.0.0.1:82/login")
assert response.text == '{"Whether login succeeded":true}'
# 当在本机请求 http://127.0.0.1:82/login2 的时候,模拟服务就会响应 {"Whether login succeeded":false}
response = requests.get("http://127.0.0.1:82/login2")
assert response.text == '{"Whether login succeeded":false}'
#当请求的路径精确匹配,没匹配到路径时,会匹配/mock_other_path
response = requests.get("http://127.0.0.1:82/test_other_path")
assert response.text == 'other_path_content'
在自动化测试中,不仅要判断被测程序处理依赖服务的响应数据是否正确,还要判断被测程序请求依赖服务时发送的请求数据是否正确。这正是这个功能点的使用场景
from mock import MockServerClass
import requests
def test_query_mock_server_request_data():
mockserver = MockServerClass(mock_server_port=82)
mockserver.add_route(route_path="/login", response_status='200', response_headers='{"test_key":"test_value"}',
response_date='{"Whether login succeeded":true}')
mockserver.start_mock_server()
response = requests.post("http://127.0.0.1:82/login", data="test1",headers={"Aa":"bb"})
#mock_server_request_data_len是MockServerClass类的属性,记录的是被请求的总次数
#断言模拟服务一共收到一个请求
assert mockserver.mock_server_request_data_len == 1
#get_mock_server_request_data_list()返回的是一个列表包含模拟服务收到的所有请求数据,元素是MockServerRequestData类对象
mockserver_received_first_request = mockserver.get_mock_server_request_data_list()[0]
"""
MockServerRequestData类有四个属性解释如下
request_url是请求的uri,字符串类型
request_method是请求的http方法,字符串类型
request_body是请求的body,字符串类型
request_headers是http请求头,字典类型
"""
#断言模拟服务收到的第一个请求的请求方法是post请求
assert mockserver_received_first_request.request_method == "POST"
#断言模拟服务收到的第一个请求的url是/login
assert mockserver_received_first_request.request_url == "http://127.0.0.1:82/login"
#断言模拟服务收到的第一个请求的请求头正确
print(mockserver_received_first_request.request_headers)
assert mockserver_received_first_request.request_headers["Aa"] == "bb"
#断言模拟服务收到的请求body正确
mock_server_request_data_list = mockserver.get_mock_server_request_data_list()
assert mockserver_received_first_request.request_body == "test1"
支持在远程服务器创建模拟服务,这在自动化测试分布式系统很有用。因为这时通常要模拟多个后端服务。比如测试一致性 hash 算法,在一台机器启动模拟服务就不够了,需要在多台机器启动模拟服务。在远程服务器启动模拟服务需要在远程服务器提前安装好 flask。需要有远程服务的 ssh 登录信息。以下是使用示例
from mock import MockServerClass
import requests
def test_deploy_to_remote():
mock_server_machine = {"ip": "192.168.3.67", "port": "22", "username": "root", "password": "jiexijiexi@@"}
# 创建模拟服务的对象,这里的 mock_server_machine 是指定启动模拟服务程序的机器登录信息,mock_server_port 是指定模拟服务的监听端口
mockserver = MockServerClass(remote_mock_server_machine=mock_server_machine, mock_server_port=82)
# 这里添加一个模拟接口,接口的路径是/login,模拟服务响应的状态码是 200
mockserver.add_route(route_path="/login", response_status='200', response_headers='{"test_key":"test_value"}',
response_date='{"Whether login succeeded":true}')
# 调用 start_mock_server() 方法后,才会真正的启动模拟服务,这样当用户请求 http://192.168.3.67:82/login 的时候,模拟服务就会响应 {"Whether login succeeded":true}
mockserver.start_mock_server()
response = requests.get("http://192.168.3.67:82/login")
assert response.text == '{"Whether login succeeded":true}'
测试运行结束后,如果不调用 mockserver.stop_mock_server(),模拟服务不会自动关闭。可以通过 mockserver.stop_mock_server() 关闭模拟服务,
from mock import MockServerClass
import requests
mockserver = MockServerClass(mock_server_port=82)
mockserver.stop_mock_server()