直接测试装饰器函数会失败,因装饰器覆盖原函数的__name__、__doc__、签名及行为;应通过func.__wrapped__获取原始逻辑测试,但需先检查属性是否存在且注意多层装饰器场景。

为什么直接测试装饰器函数会失败
装饰器会覆盖原函数的 __name__、__doc__、签名甚至行为,导致单元测试里断言返回值或检查参数时实际调用的是装饰后逻辑(比如日志、权限校验),而非你真正想验证的业务逻辑。mock 也不好使——因为 mock 的是装饰后的对象,不是原始实现。
__wrapped__ 不是所有装饰器都支持
只有用 functools.wraps() 正确包装的装饰器才暴露 __wrapped__ 属性。手写的、没调用 functools.wraps 的装饰器,或者用 @cache、@lru_cache 这类内置装饰器(Python 3.9+ 才加 __wrapped__),可能根本不存在该属性,访问会抛 AttributeError。
实操建议:
- 先检查:
hasattr(my_func, '__wrapped__'),别盲目调用 - 若装饰器是你自己写的,确保内部用了
functools.wraps(func) - 第三方装饰器(如
tenacity.retry)通常不提供__wrapped__,得换思路——比如把核心逻辑拆成无装饰的私有函数再测试
测试时怎么安全拿到并调用原函数
最简方式是通过 func.__wrapped__ 获取未被装饰包裹的原始可调用对象,但要注意它可能仍带部分闭包逻辑(比如装饰器捕获了外层变量)。所以不是“完全裸奔”,而是“去掉装饰器壳,保留原函数定义时的上下文”。
示例:
from functools import wraps
<p>def log_calls(func):
@wraps(func)
def wrapper(*args, *<em>kwargs):
print(f"Calling {func.<strong>name</strong>}")
return func(</em>args, **kwargs)
return wrapper</p><p>@log_calls
def add(a, b):
"""Return a + b"""
return a + b
</p>
测试写法:
仅使用 Python 将 Claw Code 虚拟化框架与 OpenClaw 集成,提供 184 种工具和 200 多条命令,用于安全分析和代码质量...
def test_add_wrapped():
# 直接测装饰后行为(比如日志是否触发)→ 另写一个 test
pass
<p>def test_add_core_logic():</p><h1>测纯逻辑,跳过装饰器干扰</h1><pre class="brush:php;toolbar:false;">core = add.__wrapped__
assert core(2, 3) == 5
assert core.__doc__ == "Return a + b" # __doc__ 也恢复了
pytest + pytest-mock 场景下的常见坑
用 patch mock 装饰器函数时,如果目标路径写的是装饰后的函数名(如 "mymodule.add"),mock 到的是装饰器返回的 wrapper;而你想 mock 原函数内部调用(比如它依赖的 requests.get),就得确保 patch 发生在 __wrapped__ 调用链里——但这通常不必要,反而容易绕晕。
更稳妥的做法:
- 对集成行为(含装饰器)写端到端测试,用真实依赖或完整 mock
- 对核心逻辑,直接 import 并测试
add.__wrapped__,不走 patch - 如果必须 patch 原函数(比如它被其他函数调用),patch 的 target 应该是
mymodule.add.__wrapped__,但注意 pytest-mock 默认不支持这种嵌套属性 patch,得用unittest.mock.patch.object(add, '__wrapped__', ...)或提前赋值临时替换
复杂点在于:装饰器可能有多层(比如 @retry @log_calls @validate),这时 func.__wrapped__.__wrapped__ 才能剥到最内层——但没人会这么干,应该重构装饰器或拆分职责。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










