首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Python装饰器的进阶应用:实现测试接口过程可观测性

Python装饰器的进阶应用:实现测试接口过程可观测性

作者头像
小田测测看
发布2026-06-17 17:19:42
发布2026-06-17 17:19:42
980
举报

在自动化测试中,我们经常需要验证API请求的body是否正确。例如,在测试xx功能时,我们构造了初始的请求参数,但函数内部可能会根据条件修改这些参数(如合并默认选项、处理空值等)。如果只记录调用时的参数,那么当测试失败时,我们无法确定是传入参数的问题还是函数内部处理的问题。

可以设计一个装饰器, 目的是为了在自动化测试中更详细地记录关键函数的执行过程,特别是当函数内部对参数进行了修改时,我们希望记录的是修改后的最终值,而不是调用时传入的原始值。这对于调试和结果验证至关重要。

思路:

  1. 1. 我们可以在函数内部将最终生成的body作为一个特殊的属性或变量传递给装饰器。
  2. 2. 装饰器在记录时,检查函数返回值中是否包含这个特殊信息,或者通过其他方式获取。

但是,装饰器是在函数执行后记录信息的,我们可以利用这一点:在函数内部,将需要额外记录的信息(比如最终的body)附加到返回值上。

3.捕获函数内部的关键状态(如最终请求体)。

4.在Allure报告中展示这些状态,与函数参数和返回值一起形成完整的执行上下文。

以下是完整代码

代码语言:javascript
复制
# 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报告可以让测试和开发快速定位问题,特别是当参数在函数内部被修改时

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-07-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程拾光 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档