Python mock autospec:替身调用一直成功,为什么真实函数却说参数传错了

前天 4阅读

测试把一个函数换成普通 Mock 后,调用方漏掉参数也能得到假返回值,测试于是通过;换回真实函数却立刻 TypeError。这时问题可能是替身过于宽松,没有保住真实接口的调用方式。create_autospec 可以从现有函数复制签名,让一部分接口变化在测试阶段出现。

下面的 render 要求 label 按位置传入,width 只能按关键字传入。完整脚本需要 Python 3.8 及以上,使用标准库,不安装依赖,不调用外部服务。保存为 demo.py,运行 python demo.py。

Python mock autospec:替身调用一直成功,为什么真实函数却说参数传错了

AI模型生成概念图:测试夹具核对插入位置与形状,额外部件不能随意塞入;代表调用签名约束,不代表业务数据已经正确。

from unittest.mock import Mock, create_autospec

real_calls = []

def render(label: str, /, *, width: int = 8):
    real_calls.append((label, width))
    return label[:width]

loose = Mock(return_value="preview")
assert loose() == "preview"
print("plain mock accepted missing label")

strict = create_autospec(render, return_value="preview")
assert strict("abcdef", width=3) == "preview"
strict.assert_called_once_with("abcdef", width=3)
print("autospec accepted the declared call")

checks = [
    ("missing", lambda: strict()),
    ("positional width", lambda: strict("abc", 3)),
    ("keyword label", lambda: strict(label="abc")),
]
for label, call in checks:
    try:
        call()
    except TypeError:
        print(label + ": TypeError")
    else:
        raise AssertionError("invalid call was accepted: " + label)

assert strict(42, width="wide") == "preview"
assert real_calls == []
print("wrong value types accepted; real calls:", real_calls)

try:
    render(42, width="wide")
except TypeError:
    print("real implementation with wrong values: TypeError")
else:
    raise AssertionError("the real implementation unexpectedly succeeded")

先限制实参怎样对上形参

普通 Mock 没有必需的 label 也返回 preview。autospec 创建的替身则接受 strict('abcdef', width=3),并通过 assert_called_once_with 验证这次调用。这里返回值是我们手动设置的字符串,真正受检查的是调用形状,不是渲染结果。

随后的三条失败路径分别为 missing、positional width、keyword label,全部得到 TypeError。它们对应缺少必需参数、把仅限关键字的 width 放到位置参数、以及把仅限位置的 label 写成关键字。函数签名里的斜杠和星号不只是文档样式,替身也会按这些边界检查。

签名通过不表示类型与内容通过

strict(42, width='wide') 仍返回 preview。两个参数都占了合法位置,因此签名匹配成功;注解 str、int 没有变成运行时的数据验证器。紧接着 real_calls 为 [],证明到此为止替身没有执行 render 的函数体。

真正调用 render(42, width='wide') 时才发生 TypeError。这条对照提醒我们:使用 autospec 仍需为输入类型、范围、返回内容以及错误响应分别设计测试。不要拿替身的设定返回值,来证明真实实现能处理同一组业务数据。

替身需要跟着你要模拟的接口建立

例子直接从当前 render 建立规格,所以调用方与函数声明不一致时能尽早暴露。真实项目里,可以为传入的依赖对象建立 autospec,或在 patch 时使用 autospec=True;替换位置仍要对应被测代码实际查找的名称,否则替身根本不会被用到。

自动检查也有边界。对复杂对象建立规格可能需要访问属性,具有副作用的属性或动态创建的成员值得单独审查;只有实例初始化时才出现的属性,也不能总靠类对象的规格完整推知。本文只对普通函数建立替身,避免把这些对象探索问题混进基本调用实验。

把接口测试和真实行为测试配合使用

适合用 autospec 的场景包括依赖重命名参数、增加必需项或把某项改为仅限关键字之后,防止调用方继续用旧方式。迁移时先选一个重要依赖,让测试同时覆盖一次合法调用和一个代表性的错误调用,再保留少量真实实现的行为测试。不要为了追求更严格就把所有替身内部细节都断言一遍;检查应对应调用方真正依赖的契约。

资料核对日期:2026年10月2日。本文固定输入示例在本地 Python 3.12.14 实际执行,退出码为0,预期结果检查通过;这是语言行为演示,不是生产压测。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。