基于pytest+requests+allure+yaml实现接口自动化测试框架
1.项目背景和目标:
这是一个基于Python的接口自动化测试框架,主要目标是:
- 提供一个稳定、可维护的接口测试解决方案
- 实现测试用例与测试数据的解耦
- 提供详细的测试报告和日志记录
- 支持多环境配置和灵活的用例管理
2.技术栈选型
框架采用了以下核心技术:
pytest:作为测试执行引擎,提供了强大的用例管理和参数化能力
requests:处理HTTP请求,支持各种接口调用场景
allure:生成美观的测试报告,支持详细的测试步骤记录
yaml:管理配置文件和测试数据,提供更好的可读性和维护性
logging:实现完整的日志记录系统
3.项目分层架构设计
让我详细介绍这个项目的分层架构设计:
- 测试用例层 (testcases/)
- test_api.py:测试用例实现
- 用例编写和管理
- 测试流程控制
- 测试数据参数化
- 断言结果验证
import pytest
import allure
import json
import time
from datetime import datetime
from common.http_client import HTTPClient
from common.assertions import Assertions
from common.yaml_handler import YamlHandler
from common.variable_handler import VariableHandler
class TokenManager:
def __init__(self):
self.token = None
self.expire_time = None
self.yaml_handler = YamlHandler()
self.token_config = self.yaml_handler.read_yaml("data/test_data.yaml")["token_config"]
def set_token(self, token, expire_time=None):
"""
设置token和过期时间
:param token: token字符串
:param expire_time: 过期时间戳(秒)
"""
self.token = token
if expire_time:
self.expire_time = expire_time
else:
# 如果没有提供过期时间,使用配置的默认过期时间
self.expire_time = time.time() + self.token_config["expire_time"]
def is_token_valid(self):
"""
检查token是否有效
:return: bool
"""
if not self.token or not self.expire_time:
return False
# 检查是否接近过期时间
remaining_time = self.expire_time - time.time()
return remaining_time > self.token_config["refresh_before_expire"]
def get_token(self):
"""
获取token
:return: token字符串
"""
return self.token
@allure.epic("API自动化测试")
class TestAPI:
def setup_class(self):
self.http_client = HTTPClient()
self.assertions = Assertions()
self.test_data = YamlHandler.read_yaml("data/test_data.yaml")
self.token_manager = TokenManager()
self.variable_handler = VariableHandler()
def refresh_token(self):
"""
刷新token
"""
with allure.step("刷新token"):
# 获取登录测试用例数据
login_case = next(
case for case in self.test_data["test_cases"]
if case["path"] == "/api/login"
)
# 发送登录请求
response = self.http_client.request(
method=login_case["method"],
path=login_case["path"],
json=login_case["data"]
)
# 验证登录响应
assert response.status_code == 200, "登录失败,无法刷新token"
# 获取新token和过期时间
response_data = response.json()
new_token = response_data.get("token")
expire_time = response_data.get("expire_time") # 假设接口返回过期时间戳
# 更新token管理器
self.token_manager.set_token(new_token, expire_time)
allure.attach("Token已刷新", f"新token: {new_token}", allure.attachment_type.TEXT)
@allure.feature("API测试用例")
@pytest.mark.parametrize("case", YamlHandler.read_yaml("data/test_data.yaml")["test_cases"])
def test_api(self, case):
"""API测试用例"""
allure.dynamic.title(case["case_name"])
allure.dynamic.description(f"测试接口: {case['path']}")
# 如果是需要token的接口,检查token是否需要刷新
if "headers" in case and "Authorization" in case["headers"]:
if not self.token_manager.is_token_valid():
self.refresh_token()
# 替换请求数据中的变量
if "headers" in case:
case["headers"] = self.variable_handler.replace_variables(case["headers"])
if "data" in case:
case["data"] = self.variable_handler.replace_variables(case["data"])
with allure.step(f"准备请求数据"):
headers = case.get("headers", {})
if "Authorization" in headers:
headers["Authorization"] = headers["Authorization"].format(
token=self.token_manager.get_token()
)
allure.attach(
json.dumps(case.get("data", {}), ensure_ascii=False, indent=2),
"请求数据",
allure.attachment_type.JSON
)
# 发送请求
with allure.step(f"发送{case['method']}请求到 {case['path']}"):
request_kwargs = {
'method': case["method"],
'path': case["path"],
'service': case.get("service"),
'headers': case.get("headers")
}
# 添加请求数据
if case.get("data"):
if case["method"].upper() == "GET":
request_kwargs['data'] = case["data"]
else:
request_kwargs['json'] = case["data"]
response = self.http_client.request(**request_kwargs)
allure.attach(
json.dumps(response.json(), ensure_ascii=False, indent=2),
"响应数据",
allure.attachment_type.JSON
)
# 保存需要的变量
if "save_variables" in case and response.status_code == 200:
with allure.step("保存接口返回变量"):
self.variable_handler.save_variables(
response.json(),
case["save_variables"]
)
# 断言处理
expected = case["expected"]
with allure.step("执行断言"):
if "status_code" in expected:
with allure.step(f"验证状态码: 期望 {expected['status_code']}"):
self.assertions.assert_status_code(response, expected["status_code"])
if "contains_fields" in expected:
for field in expected["contains_fields"]:
with allure.step(f"验证字段存在: {field}"):
self.assertions.assert_contains_field(response, field)
if "field_values" in expected:
for field, value in expected["field_values"].items():
with allure.step(f"验证字段值: {field} = {value}"):
self.assertions.assert_field_value(response, field, value)
# 如果是登录接口,保存token
if case["path"] == "/api/login" and response.status_code == 200:
with allure.step("保存登录token"):
response_data = response.json()
self.token_manager.set_token(