spy比patch更适合监控已有逻辑,因为它不替换原函数,仅记录调用信息而不干预执行,避免行为脱节和副作用屏蔽;spy不可设return_value,应使用wraps实现监控+控制。

为什么 spy 比 patch 更适合监控已有逻辑
当你只是想确认某个函数被调用了、传了什么参数、返回了什么值,但又不想切断它的真实行为时,mocker.spy() 是更轻量、更安全的选择。相比 mocker.patch(),它不替换原函数,因此不会因 patch 失败或遗漏导致测试环境与生产行为脱节;也不会因 patch 范围过大(比如 patch 了整个模块)而意外屏蔽掉你本想保留的副作用。
spy 的典型误用:把 spy 当成 patch 用
常见错误是写完 mocker.spy(obj, 'method') 后,还手动给 obj.method.return_value = ... —— 这毫无意义,因为 spy 不接管返回值。它只记录调用,不干预执行。
- spy 对象没有
return_value属性(设了也不生效) - spy 对象的
call_count、call_args、called_with()等统计属性可用,但不能用assert_called_once_with()去断言“未发生”的调用(会抛AssertionError,而非静默失败) - 若需同时监控 + 控制返回值,应改用
mocker.patch.object(obj, 'method', wraps=obj.method),而非 spy
spy 在真实项目中的三个高价值场景
spy 最有用的地方,往往不是“验证是否调用”,而是“验证调用是否符合预期上下文”。
-
验证重试逻辑:mock 一个网络请求函数为 spy,然后触发业务逻辑,再检查
spy.call_count == 3且每次call_args中的超时参数递增 -
确认日志埋点位置:对
logging.getLogger().info打 spy,确保关键路径上某条 info 日志只在特定条件满足时发出 -
调试异步回调链:对回调函数打 spy,配合
asyncio.run()或 pytest-asyncio,观察实际被调用的时机和参数,避免因事件循环调度导致的“看似没调用”假象
容易被忽略的生命周期细节
spy 对象本身不自动清理调用记录 —— 每次测试运行后,它的 call_count 和 call_args_list 仍保留上次结果。这会导致后续测试断言失败,除非你显式重置:
立即学习“Python免费学习笔记(深入)”;
- 推荐做法:在每个测试末尾加
spy.reset_mock() - 不推荐依赖 fixture 自动重置:pytest-mock 的
mockerfixture 不管理 spy 的状态,只管理 patch 的自动撤销 - 注意
reset_mock()不会清除spy_return(最后一次返回值),如需清空,得手动赋值spy.spy_return = None


















