在自动化测试中,我们经常需要验证API请求的body是否正确。例如,在测试xx功能时,我们构造了初始的请求参数,但函数内部可能会根据条件修改这些参数(如合并默认选项、处理空值等)。如果只记录调用时的参数,那么当测试失败时,我们无法确定是传入参数的问题还是函数内部处理的问题。
可以设计一个装饰器, 目的是为了在自动化测试中更详细地记录关键函数的执行过程,特别是当函数内部对参数进行了修改时,我们希望记录的是修改后的最终值,而不是调用时传入的原始值。这对于调试和结果验证至关重要。
思路:
body作为一个特殊的属性或变量传递给装饰器。但是,装饰器是在函数执行后记录信息的,我们可以利用这一点:在函数内部,将需要额外记录的信息(比如最终的body)附加到返回值上。
3.捕获函数内部的关键状态(如最终请求体)。
4.在Allure报告中展示这些状态,与函数参数和返回值一起形成完整的执行上下文。
以下是完整代码
# test_example.py
import requests
import pytest
import functools
import os
import inspect
import json
import allure
from typing importAny, Dict, Optional
# 假设的 ResponseObject 类型
class ResponseObject:
def __init__(self, details):
self.details = details
# 记录消息到 Allure
def allure_record_msg(description: str, message: Dict[str, Any]):
"""记录结构化消息到 Allure 报告"""
with allure.step(description):
allure.attach(
json.dumps(message, indent=4, ensure_ascii=False),
name="Function Details",
attachment_type=allure.attachment_type.JSON
)
def allure_operation(desc: str, record=True):
"""增强版装饰器,支持捕获函数内部最终变量"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 为每次调用创建独立的存储容器
internal_vars = {}
setattr(wrapper, "_internal_vars", internal_vars)
# 设置描述信息
setattr(wrapper, "desc", desc)
# 执行环境控制
env_var = os.environ.get("setup_teardown", "")
os.environ["setup_teardown"] = "true"
try:
ret = func(*args, **kwargs)
finally:
os.environ["setup_teardown"] = env_var
# 获取捕获的内部变量
captured_vars = getattr(wrapper, "_internal_vars", {})
# 清理临时属性
if hasattr(wrapper, "_internal_vars"):
delattr(wrapper, "_internal_vars")
# 处理函数返回结果
result = ret.details if isinstance(ret, ResponseObject) else ret
# 处理参数序列化问题
args_new = []
for item in args:
try:
json.dumps({"item": item}, indent=4, ensure_ascii=False)
args_new.append(item)
except Exception:
args_new.append(str(item))
# 处理默认参数和关键字参数
try:
func_detail = inspect.getfullargspec(func)
except AttributeError:
func_detail = inspect.getargspec(func)
param_names = func_detail.args
if func_detail.defaults:
defaults_len = len(func_detail.defaults)
required_args = param_names[:-defaults_len]
# 处理位置参数覆盖默认参数的情况
if len(args) > len(required_args):
extra_args = args[len(required_args):]
extra_params = param_names[len(required_args):len(required_args) + len(extra_args)]
kwargs.update(dict(zip(extra_params, extra_args)))
args = args[:len(required_args)]
# 填充未提供的默认参数
default_params = param_names[-defaults_len:]
default_values = func_detail.defaults
for param, value inzip(default_params, default_values):
if param notin kwargs:
kwargs[param] = value
# 记录执行详情
if not os.environ.get("setup_teardown") and record:
message = {
"function": func.__name__,
"description": desc,
"arguments": {
"positional": args_new,
"keyword": kwargs
},
"return_value": result,
"internal_state": captured_vars
}
# 添加函数文档
if func.__doc__:
message["documentation"] = func.__doc__.strip()
# 获取步骤描述
step_desc = desc
if hasattr(wrapper, "desc"):
step_desc = getattr(wrapper, "desc")
# 记录到 Allure
allure_record_msg(step_desc, message)
return ret
return wrapper
return decorator
@allure_operation("调用考勤管理 API[AttendanceManagementList] - 获取指定列表")
def attendance_manage_list(body):
# 比如这里模拟处理点逻辑
if body['user.age']>20:
body['user.age'] = 18
# ========= 关键部分 =========
# 捕获最终 body 信息到装饰器
try:
# 获取当前函数的包装器
func_wrapper = attendance_manage_list
# 确保装饰器属性存在
if hasattr(func_wrapper, "_internal_vars"):
# 存储最终 body 和任何其他需要的信息
getattr(func_wrapper, "_internal_vars")["final_body"] = body
getattr(func_wrapper, "_internal_vars")["processed_nas_infos"] = body['user.age']
except Exception as e:
print(f"捕获内部变量失败: {e}")
# ==========================
response = requests.post(url = "http://httpbin.org/post", json=body)
print(response.text)
return response.json()
def test_api():
attendance_manage_list(body={
"user.name": "拾光",
"user.age": 28,
"details.hobbies": ["reading", "swimming"]
})
if __name__ == '__main__':
pytest.main(['-vs','--alluredir',"report",__file__])运行结果示例:

代码完整截图

这样的话,通过allure报告可以让测试和开发快速定位问题,特别是当参数在函数内部被修改时