KernelRanch
返回文章列表

Python 测试替身为什么没生效:在调用方查找的名字上打补丁

2026-11-13 #python#testing#unittest

被测模块执行 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 patchhttps://docs.python.org/3.11/library/unittest.mock.html#where-to-patch在使用方查找的命名空间替换绑定
Python unittest.mock Autospeccinghttps://docs.python.org/3.11/library/unittest.mock.html#autospeccing基于真实对象创建带签名约束的替身

核验说明

  • Windows、Python 3.11.9 验证了补丁目标选错、正确拦截、缺少关键字参数时抛出 TypeError,以及离开补丁作用域后恢复原绑定。