Python 用 python 的 flask 写 mock-server, 解决自动化测试中依赖服务的问题

test · 2021年10月08日 · 最后由 test 回复于 2021年10月11日 · 5641 次阅读

背景

    在测试后端的服务器程序时,经常会遇到测试 A 程序的接口时,A 程序要通过 http 协议访问 B 程序。B 程序返回的数据不一样时,A 程序执行的代码逻辑不一样。而且有些场景,B 程序正常情况下是不容易出现的。比如 http 响应超时,响应非法的数据。所以需要一种方法,可以方便的模拟 B 程序的各种不同的响应。解决依赖服务的方法很多。比如用 nginx 搭建一个静态的 http 服务器。还有其它类似的工具。它门的使用方式大多数都是启动一个进程,然后指定一个模拟响应数据的配置文件

如果只是手动测试。用 nginx 之类的工具来模拟是没问题的。如果是用与自动化测试会有以下缺点:

  • 不方便动态改变响应数据,比如第 1 个用例需要依赖服务响应的状态码是 200,第 2 个用例需要依赖服务响应的状态码是 500。如果要在自动化代码里完成这些,需要在用例 1 里去动态修改配置文件返回 200。在用例 2 里再次去修改配置文件返回 500,这就比较麻烦了。

  • 不方便查询模拟服务接收到的请求数据。比如说 A 程序有个功能是通过 http 协议调 B 程序的鉴权接口。现在需要验收 A 程序请求 B 程序接口的所有请求数据,是否全部都符合接口文档。如果是手动测试的话,直接抓包看就可以了。但是要在自动化测试里完成这个用例,就不好用抓包了。当然如果用 nginx 来做模拟服务,也可以在自动化代码里抓取 nginx 的日志回来,再解析日志来断言请求数据是否正确。然而这不够方便

我期望在自动化测试中模拟服务效果如下:

  • 可以很方便的通过调一个函数就可以动态修改响应数据
  • 可以很方便的通过调一个函数就获取到模拟服务被请求的请求数据

为了达到这个目的,写了这个 auto-mock-server。已上传到 pypi

auto-mock-server 有如下功能:

1.设置 http 响应的状态码

2. 设置 http 响应的响应头

3.设置 http 响应的 body

4.设置 http 响应的延时时间,用于测试依赖服务响应超时的场景

5.可以在一个模拟服务配置多个不同路径的接口,即可以实现根据不同的请求路径响应不同的数据

6.可以获取模拟服务被请求的请求数据,包括请求方法,请求 url,请求头,请求 body,请求总次数

7.可以把模拟服务部署到远程的机器运行

8.支持关闭模拟服务

auto-mock-server 的使用说明

运行环境:需要安装 python3,在 windox 或者 linux 系统运行

先安装 auto-mock-server,直接 pip install auto-mock-server -i https://pypi.douban.com/simple

以下是 1~4 功能点的使用示例

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


第 5 个功能点的使用示例

可以在一个模拟服务配置多个不同路径的接口,即可以实现根据不同的请求路径响应不同的数据

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'

第 6 个功能点的使用示例。查询模拟服务接口被请求时的数据

在自动化测试中,不仅要判断被测程序处理依赖服务的响应数据是否正确,还要判断被测程序请求依赖服务时发送的请求数据是否正确。这正是这个功能点的使用场景

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"


第 7 个功能点,在远程服务器创建一个模拟服务

支持在远程服务器创建模拟服务,这在自动化测试分布式系统很有用。因为这时通常要模拟多个后端服务。比如测试一致性 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()


共收到 4 条回复 时间 点赞

很赞,提个建议,是否可以支持配置 route 到真实 server,这样可以支持真实 respond 和 mock respond 的切换。

Jacky 回复

你好,暂时没有做 route 到真实 server 这个,你想的是某些自动化用例里对接真实的 server 是吧。我现在是把模拟服务和真实服务部署到同一台机器,然后在想对接真实服务的用例里把模拟服务关调,启动真实的服务,这样就可以对接到真实服务了。你说的这个方式好像是方便一点。后面考虑增加一下。

mac 系统运行第一个会报错

Linux 下也报错

mac 系统使用修改这个函数 将 p 去掉就可以使用了

test #4 · 2021年10月11日 Author
caleb 回复

由于公司的电脑是 windox,没在 mac 运行过,上面文章也写了要在 windox 或者 linux 运行。linux 报错是某次改代码改错了,感谢提醒,我只测试了 windox 本地运行,远程 linux 运行,没测 linux 本地运行,所以没发现,用 pip3 install --upgrade auto-mock-server 升级到最新的 1.0.9 的版本就好了

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册