Python 测试替身为什么没生效:在调用方查找的名字上打补丁
被测模块执行 from gateway import charge 后,会在自己的命名空间查找 charge。此时替换 gateway.charge,已经导入的绑定仍然指向原函数。测试要把补丁打在调用时查找的名字上,再用 autospec=True 让调用参数遵守真实签名。
1. 本机复现
保存为 mock_lab.py,运行 python mock_lab.py。为便于复现,脚本用 ModuleType 构造两个模块,模拟两个文件之间的导入。网关函数只返回固定字符串。
from types import ModuleType
from unittest.mock import patch
gateway = ModuleType("lab_gateway")
checkout = ModuleType("lab_checkout")
exec(
"def charge(amount, *, currency):\n"
" return f'real:{amount}:{currency}'\n",
gateway.__dict__,
)
with patch.dict("sys.modules", {"lab_gateway": gateway, "lab_checkout": checkout}):
exec(
"from lab_gateway import charge\n"
"def pay(amount):\n"
" return charge(amount, currency='CNY')\n",
checkout.__dict__,
)
with patch("lab_gateway.charge", autospec=True, return_value="stub") as wrong:
result = checkout.pay(25)
assert result == "real:25:CNY"
wrong.assert_not_called()
print("patch definition:", result)
with patch("lab_checkout.charge", autospec=True, return_value="stub") as stub:
result = checkout.pay(25)
assert result == "stub"
stub.assert_called_once_with(25, currency="CNY")
print("patch lookup:", result)
try:
checkout.charge(25)
except TypeError:
print("missing keyword: rejected by autospec")
else:
raise AssertionError("accepted invalid signature")
assert checkout.pay(25) == "real:25:CNY"
print("after patch:", checkout.pay(25))
本地实测输出如下。
patch definition: real:25:CNY
patch lookup: stub
missing keyword: rejected by autospec
after patch: real:25:CNY
2. 沿着调用表达式定位补丁
| 被测模块的调用方式 | 通常的补丁位置 |
|---|---|
from gateway import charge,随后调用 charge(...) | 被测模块的 charge |
from gateway import charge as send,随后调用 send(...) | 被测模块的 send |
import gateway,随后调用 gateway.charge(...) | gateway 模块对象上的 charge |
| 依赖已绑定为默认参数或保存在实例里 | 调用时注入测试替身,或替换实际持有的实例依赖 |
如果断言显示 mock 从未调用,检查导入形式、别名和绑定时机。正确替换后,既要断言业务结果,也要断言替身收到的参数。autospec 可以捕获签名不符的调用。远端返回结构、超时和实际连接行为仍需要有明确输入输出的契约测试或集成测试。
小结
根据执行时的查找路径定位补丁。用 autospec 检查调用形状,用断言验证替身确实进入执行路径。
数据来源与检索时间
检索时间 2026-11-13。
| 来源 | URL | 对应内容 |
|---|---|---|
| Python unittest.mock Where to patch | https://docs.python.org/3.11/library/unittest.mock.html#where-to-patch | 在使用方查找的命名空间替换绑定 |
| Python unittest.mock Autospeccing | https://docs.python.org/3.11/library/unittest.mock.html#autospeccing | 基于真实对象创建带签名约束的替身 |
核验说明
- Windows、Python 3.11.9 验证了补丁目标选错、正确拦截、缺少关键字参数时抛出 TypeError,以及离开补丁作用域后恢复原绑定。