应显式调用装饰器函数传入测试函数,检查返回的装饰后对象行为;对带参装饰器需先调用外层再传函数;用pytest.raises验证运行时异常;用inspect.signature比对签名一致性;通过依赖注入或patch隔离副作用。

如何用 pytest 测试装饰器是否修改了函数行为
直接测试装饰器逻辑,关键不是“它有没有被加”,而是“它加完之后函数是否按预期变了”。pytest 本身不提供装饰器专用断言,但你可以把装饰器当成普通函数调用,传入一个测试用的函数,再检查返回值(即被装饰后的函数)的行为。
- 别写
@my_decorator然后测原函数——那测的是装饰后整体效果,掩盖了装饰器本身的逻辑 - 应该显式调用
my_decorator(test_func),拿到装饰后的对象再做断言 - 注意:如果装饰器带参数(如
@retry(max_attempts=3)),要先调用外层返回装饰器,再传函数,例如retry(max_attempts=3)(test_func) - 对返回
functools.wraps包装的装饰器,记得检查__name__、__doc__是否保留,否则测试里用assert decorated.__name__ == "test_f"会失败
如何捕获装饰器引发的异常(比如权限校验失败)
很多装饰器会在运行时抛异常(如 @requires_role("admin") 在非 admin 调用时 raise PermissionError)。pytest 的 pytest.raises 是最自然的验证方式,但要注意触发时机。
- 必须调用被装饰后的函数,不能只定义或获取它——
decorated = my_decorator(f)不会触发异常,decorated()才会 - 若装饰器在闭包内做了延迟检查(比如记录日志但不立即执行),可能需要 mock 或 patch 相关依赖才能触发异常分支
- 示例:
def test_requires_admin_raises_permission_error(): decorated = requires_role("admin")(lambda: None) with pytest.raises(PermissionError, match="insufficient role"): decorated() # 注意这里才是调用点
如何测试装饰器对函数签名(signature)的影响
装饰器常破坏原始函数的 inspect.signature,导致 IDE 提示错乱、类型检查失效、甚至 functools.partial 行为异常。这不是功能 bug,但属于逻辑正确性的一部分。
- 用
inspect.signature(decorated_func)对比原始函数,确认参数名、默认值、kind 是否一致 - 若用了
functools.wraps(func),大部分信息会被保留;但若手动重写了__call__(比如类装饰器),需额外处理__signature__ - 常见坑:用
lambda或def wrapper(*args, **kwargs)但没设__signature__,会导致inspect.signature(wrapper)返回(*args, **kwargs),丢失原始信息 - 可加断言:
import inspect orig_sig = inspect.signature(original_func) dec_sig = inspect.signature(decorated_func) assert orig_sig == dec_sig
如何隔离测试带副作用的装饰器(如缓存、日志、数据库连接)
装饰器如果依赖外部状态(比如 @cache 用全局 dict、@log_calls 写文件),测试容易污染、不可重复。这时不能靠“重置模块”这种粗暴方式,而应控制装饰器内部依赖的注入点。
立即学习“Python免费学习笔记(深入)”;
- 优先让装饰器支持传入可替换的依赖,例如
@cache(storage=MockStorage()),测试时传 mock - 若已写死(如直接用
LRUCache),用unittest.mock.patch替换模块级变量,但注意 patch 的 target 必须是装饰器实际导入/使用的路径,不是定义路径 - 避免在测试中 import 装饰器所在模块后再 patch——很可能 patch 失败,因为装饰器已在 import 时完成包装。应在测试前 patch,或用
importlib.reload(慎用) - 日志类装饰器建议统一使用
logging.getLogger(__name__),然后在测试中用caplogfixture 捕获,而不是 assert 文件内容
测试装饰器最难的不是语法,而是厘清“谁在什么时候做了什么”:是装饰时改了函数对象?还是调用时才介入?这个时机差一点,assert 就会打在空处。


















