pytest中test函数被装饰后报该错,根本原因是装饰器未使用@functools.wraps,导致pytest无法获取原函数签名和参数信息,进而无法注入fixture或识别测试函数。

pytest 里 test 函数被装饰后报 TypeError: test_XXX() missing 1 required positional argument
这是最典型的信号:装饰器没用 @functools.wraps,导致 pytest 拿不到原函数的签名(__signature__)和参数信息。pytest 在收集测试时会尝试检查函数签名以支持参数化或 fixture 注入,一旦装饰后 test_foo 的 __name__、__doc__、__annotations__ 全变成装饰器内部函数的了,参数列表也丢失,就会在运行前就抛这个错。
解决方式很简单,但必须做:
- 所有自定义装饰器里,只要包装了测试函数,就必须在内层函数上加
@functools.wraps(func) - 别只写
def wrapper(*args, **kwargs):就完事,漏掉wraps就等于没修 - 如果用了
functools.partial或闭包传参,也要确保最终返回的可调用对象被wraps包裹过
为什么 @wraps 不是“可选优化”,而是 pytest 的硬性依赖
pytest 不只是靠函数名找测试,它依赖 inspect.signature() 解析参数来决定是否注入 capsys、tmp_path 等 fixture;也依赖 __name__ 和 __doc__ 生成测试 ID 和报告。没有 wraps,inspect.signature(wrapped_func) 会返回 (*args, **kwargs),而不是真实签名,导致:
- fixture 名称无法匹配(比如你写了
def test_x(db),但装饰后 pytest 看不到db参数) - 参数化(
@pytest.mark.parametrize)失败,因为 pytest 无法把参数名映射到函数形参 - IDE(如 PyCharm)无法识别为测试函数,不显示运行按钮
这不是兼容性问题,是 pytest 内部机制直接失效。
立即学习“Python免费学习笔记(深入)”;
带参数的装饰器怎么正确套 @wraps
常见错误是把 @wraps 放在最外层闭包里,或者只 wraps 了中间函数,没 wraps 到最终返回的函数。正确结构长这样:
from functools import wraps
<p>def my_decorator(prefix="test"):
def decorator(func):
@wraps(func) # ← 关键:这里 wraps 原始 func
def wrapper(*args, *<em>kwargs):
print(f"[{prefix}] Running {func.<strong>name</strong>}")
return func(</em>args, **kwargs)
return wrapper
return decorator</p><p>@my_decorator("DEBUG")
def test_something():
assert True
注意点:
-
@wraps(func)必须紧贴def wrapper,不能写成@wraps(decorator)或漏掉 - 如果装饰器本身还接受配置(比如上面的
prefix),wraps仍要作用于最内层包装函数所接收的func,不是外层闭包 - 用
help(test_something)或test_something.__name__验证是否生效:结果应是'test_something',而非'wrapper'
第三方装饰器(比如 @lru_cache)和 pytest 冲突怎么办
@lru_cache、@property、@cached_property 这类内置/标准装饰器默认不保留签名,直接套在 test 函数上大概率让 pytest 报错或跳过测试。处理原则就一条:
- 不要直接用它们装饰测试函数本身 —— 把逻辑拆出去,只在测试函数内部调用被缓存的函数
- 如果真要保留缓存又想测,用
@functools.wraps手动重写一层薄包装(哪怕只是透传) - 避免组合多个非
wraps装饰器,比如@lru_cache @retry套在 test 上,基本不可测
真正难缠的是某些 ORM 或框架自带的装饰器(比如 Django 的 @transaction.atomic),它们往往不暴露源码也不支持 wraps。这种时候优先查文档看是否有 pytest 兼容模式,其次考虑改用 fixture(例如用 django_db fixture 替代手动事务装饰)。
装饰器不是黑盒,只要它改变了函数对象,pytest 就可能“认不出”——而 @wraps 是唯一能让它继续认出来的胶水。漏掉它,后面所有 fixture、参数化、标记都可能静默失效。


















