spy是pytest-mock提供的监控机制,可记录函数调用(次数、参数等)但不改变其原始行为;而mock.patch默认完全替换目标函数,需手动设置side_effect才能转发调用。

spy是什么,和mock.patch有什么区别?
pytest-mock 本身不提供原生 spy 功能——它只是 unittest.mock 的封装层。所谓“spy”,指的是既能记录调用行为(如次数、参数),又不干扰原函数执行的机制。Python 标准库的 Mock 默认会拦截并替换目标,而真正实现 spy 行为,得靠 side_effect + 原函数转发。
常见误区是以为 mocker.spy() 是独立功能,其实它是 pytest-mock 提供的一个便捷包装,底层仍调用 Mock 并设置 side_effect 指向原函数。
-
mocker.spy(obj, "method")返回一个Mock实例,该实例:- 自动把调用转发给原
obj.method - 同时记录所有调用(
call_count、call_args_list等可用)
- 自动把调用转发给原
- 它不等价于
mocker.patch:后者默认完全替换,需手动设side_effect才能转发
如何正确使用mocker.spy()验证调用次数?
关键点:必须确保被 spy 的对象在测试中真实被调用,而不是被其他 patch 覆盖或导入路径错误。
- 使用前确认目标函数的导入路径与 spy 路径一致(比如在
test_module.py中 spyutils.helper,就要写mocker.spy(utils, "helper"),不是mocker.spy(myapp.utils, "helper")) -
spy返回的对象可直接断言:spy = mocker.spy(module, "some_function") some_function() # 触发调用 assert spy.call_count == 1
- 若函数返回值重要,
spy不影响它;但若函数有副作用(如修改全局状态),这些副作用依然发生
容易踩的坑:
- 在同一个模块里先
@patch("module.func"),再mocker.spy(module, "func")→ 后者失效,因为 patch 已接管符号 - 对类方法 spy 时传错对象:应 spy 类本身(
mocker.spy(MyClass, "method")),而非实例(除非你明确要 spy 实例绑定方法)
call_count不准?检查是否触发了真正的调用路径
call_count 为 0 很可能不是 spy 写错了,而是测试没走到那行代码。
- 检查被测函数内部是否条件跳过、提前 return 或抛异常中断流程
- 查看日志或加临时
print确认目标函数是否真被执行(注意:不要在 spy 对象上 print,它本身不执行逻辑) - 使用
spy.assert_called()或spy.assert_called_once()比直接读call_count更健壮,它们会在失败时给出清晰提示
示例场景:
def process_items(items):
for item in items:
validate(item) # ← 这个要 spy
return len(items)
<h1>测试</h1><p>def test_process_items_calls_validate_twice(mocker):
spy = mocker.spy(module, "validate")
process_items([1, 2])
assert spy.call_count == 2</p>
如果 items 是空列表,call_count 就是 0 —— 这不是 spy 的问题,是逻辑路径没覆盖。
spy和monkeypatch混用时要注意什么?
pytest-mock 的 mocker fixture 和 monkeypatch 都能改对象,但行为不同:
-
mocker.spy()是轻量级监听,不改变绑定关系 -
monkeypatch.setattr(obj, "attr", new_value)是硬替换,会彻底覆盖原值
二者同时作用于同一目标时,后应用的生效。比如:
# 先 spy spy = mocker.spy(mod, "func") # 再 monkeypatch 替换 monkeypatch.setattr(mod, "func", lambda: "fake") # 此时 spy.call_count 永远为 0,因为原 func 根本没执行
所以:
- 只需要观测调用 → 用
mocker.spy() - 需要替换行为 + 同时记录 → 改用
mocker.patch(..., side_effect=original_func) - 不要交叉使用 spy 和 monkeypatch 修改同一个属性,除非你清楚控制顺序和生命周期
实际项目里,spy 最适合验证“是否按预期触发了某个工具函数”,而不是替代逻辑分支。它的价值在于低侵入性 —— 你不用重写业务逻辑,就能确认协作关系是否成立。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











