实现一个支持 多系统共存 的接口测试框架,同时结合 YML 文件编写测试用例,需要考虑以下关键点:
-
每个系统的独立性:
- 各系统可以有独立的配置(如
conftest.py
、环境变量等)。 - 测试用例按系统和模块组织。
- 各系统可以有独立的配置(如
-
统一管理和执行:
- 测试用例可以按系统、模块或标签(如优先级)动态选择执行。
- 支持共享框架的核心逻辑(如用例执行、结果收集、上下文管理)。
-
YML 用例兼容性:
- 每个系统的 YML 用例格式一致,支持用例依赖、动态参数、全局变量等。
-
统一测试报告和通知:
- 生成统一的测试报告,并按系统归类统计结果。
- 支持发送钉钉、邮件等通知。
1. 目录结构设计
以下目录结构将支持多个系统的接口测试共存,并利用 YML
管理测试用例:
tests/
├── system_a/
│ ├── conftest.py # system_a 的独立配置
│ ├── test_cases.yml # system_a 的 YML 测试用例
│ ├── test_system_a.py # system_a 的测试逻辑
│ ├── __init__.py
├── system_b/
│ ├── conftest.py # system_b 的独立配置
│ ├── test_cases.yml # system_b 的 YML 测试用例
│ ├── test_system_b.py # system_b 的测试逻辑
│ ├── __init__.py
├── framework/
│ ├── base_test.py # 通用测试框架逻辑
│ ├── conftest_loader.py # 动态加载 conftest.py
│ ├── reporter.py # 测试报告生成与通知
│ ├── utils.py # 工具函数,如请求处理、动态参数解析
│ ├── context.py # 上下文管理
│ ├── __init__.py
├── conftest.py # 主 conftest.py,加载动态配置
2. YML 用例格式设计
每个系统的测试用例使用统一的 YML 格式,支持以下功能:
- 基础请求配置:接口地址、请求方法、头部、参数等。
- 用例依赖:支持用例之间的依赖,并共享动态数据。
- 动态参数解析:支持
$global
和$response
占位符。 - 多用例管理:多个测试用例可以写在一个 YML 文件中。
示例 YML 文件
tests/system_a/test_cases.yml
test_cases:
- name: "创建用户"
description: "测试 system_a 创建用户接口"
request:
method: POST
url: "http://system_a.example.com/api/users"
headers:
Content-Type: "application/json"
body:
username: "user_a"
password: "password_a"
expected:
status_code: 201
body:
id: "$response.id"
save:
user_id: "id"
- name: "获取用户信息"
depends_on: "创建用户"
request:
method: GET
url: "http://system_a.example.com/api/users/$global.user_id"
headers:
Authorization: "Bearer $global.token"
expected:
status_code: 200
body:
username: "user_a"
tests/system_b/test_cases.yml
test_cases:
- name: "登录"
description: "测试 system_b 登录接口"
request:
method: POST
url: "http://system_b.example.com/api/login"
headers:
Content-Type: "application/json"
body:
username: "admin"
password: "password123"
expected:
status_code: 200
body:
token: "$response.token"
save:
token: "token"
- name: "获取数据"
depends_on: "登录"
request:
method: GET
url: "http://system_b.example.com/api/data"
headers:
Authorization: "Bearer $global.token"
expected:
status_code: 200
body:
data: []
3. 统一测试框架实现
3.1 全局上下文管理
全局上下文用于存储动态变量(如全局 token)和测试结果,支持跨用例、跨系统共享。
framework/context.py
class Context:
def __init__(self):
self.global_data = {} # 存储全局变量
self.results = {} # 存储测试结果
def set_global(self, key, value):
self.global_data[key] = value
def get_global(self, key):
return self.global_data.get(key)
def add_result(self, case_name, result):
self.results[case_name] = result
def get_results(self):
return self.results
3.2 用例加载器
动态加载每个系统的 YML 测试用例。
framework/utils.py
import yaml
def load_test_cases(file_path):
"""
加载 YML 测试用例
:param file_path: 测试用例文件路径
:return: 测试用例列表
"""
with open(file_path, "r", encoding="utf-8") as f:
data = yaml.safe_load(f)
return data["test_cases"]
3.3 用例执行器
负责处理请求、验证结果和保存动态数据。
framework/base_test.py
import requests
from framework.context import Context
class TestFramework:
def __init__(self, context):
self.context = context
def execute_test_case(self, case):
# 动态解析请求参数
request_data = self._resolve_placeholders(case["request"])
response = requests.request(
method=request_data["method"],
url=request_data["url"],
headers=request_data.get("headers"),
json=request_data.get("body")
)
# 验证响应
self._validate_response(response, case["expected"])
# 保存动态数据
if "save" in case:
for key, path in case["save"].items():
value = self._get_nested_value(response.json(), path.split('.'))
self.context.set_global(key, value)
# 保存结果
self.context.add_result(case["name"], {
"status": "passed" if response.status_code == case["expected"]["status_code"] else "failed",
"response": response.json()
})
def _resolve_placeholders(self, data):
# 解析占位符(如 $global 和 $response)
# 示例实现略,参考前述内容
pass
def _validate_response(self, response, expected):
# 验证响应状态码和内容
assert response.status_code == expected["status_code"]
for key, value in expected.get("body", {}).items():
assert response.json().get(key) == value
def _get_nested_value(self, data, keys):
# 根据路径获取嵌套的数据
for key in keys:
data = data.get(key)
return data
3.4 动态加载 conftest.py
动态加载每个系统的 conftest.py
文件。
framework/conftest_loader.py
import os
import importlib.util
import sys
def load_conftest_files(base_dir):
for root, _, files in os.walk(base_dir):
if "conftest.py" in files:
conftest_path = os.path.join(root, "conftest.py")
module_name = os.path.basename(root) + "_conftest"
spec = importlib.util.spec_from_file_location(module_name, conftest_path)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
主 conftest.py
import os
from framework.conftest_loader import load_conftest_files
base_dir = os.path.dirname(__file__)
load_conftest_files(base_dir)
3.5 pytest 集成
tests/system_a/test_system_a.py
import pytest
from framework.base_test import TestFramework
from framework.utils import load_test_cases
from framework.context import Context
context = Context()
framework = TestFramework(context)
test_cases = load_test_cases("tests/system_a/test_cases.yml")
@pytest.mark.parametrize("case", test_cases, ids=[case["name"] for case in test_cases])
def test_system_a(case):
framework.execute_test_case(case)
tests/system_b/test_system_b.py
import pytest
from framework.base_test import TestFramework
from framework.utils import load_test_cases
from framework.context import Context
context = Context()
framework = TestFramework(context)
test_cases = load_test_cases("tests/system_b/test_cases.yml")
@pytest.mark.parametrize("case", test_cases, ids=[case["name"] for case in test_cases])
def test_system_b(case):
framework.execute_test_case(case)
4. 测试执行
执行所有系统测试
pytest -v
仅执行 system_a
测试
pytest -k "system_a"
5. 测试报告与通知
可参考之前的 钉钉/邮件通知 实现,生成统一的测试报告并发送。
6. 总结
通过上述框架设计,实现了以下目标:
- 多系统共存:每个系统独立管理测试用例和配置,动态加载。
- YML 用例灵活性:支持用例依赖、动态参数解析和全局变量共享。
- 统一执行与报告:支持统一执行所有系统的测试,并生成统一的测试报告。
这种设计非常适合复杂接口测试场景,具有高扩展性和可维护性。