跳转到主内容
极星编程网:以代码为星,赴技术山海!

Python 单元测试 mock 的分层原则

mock 应封装成 pytest fixture 而非在测试函数中重复使用 @patch;fixture 需用 yield + with 确保自动还原,命名应表达意图(如 fake_api_response),并注意 patch 路径必须是使用处而非定义处。 mock 应该放在哪一层:测试代码里直接写
patch
还是封装成 fixture? mock 的位置直接决定可维护性和耦合度。在测试函数里反复写
@patch('xxx.yyy')
或
mock.patch(...)
,会导致 patch 路径散落、重复、难定位——尤其当被测模块路径重构时,一堆测试会悄无声息地失效(因为 patch 的路径根本没被调用,但 mock 依然“成功”了)。 更稳妥的做法是把 patch 封装进 pytest fixture,统一管理路径和返回值:
import pytest from unittest.mock import MagicMock @pytest.fixture def mock_requests_get(): with mock.patch('requests.get') as m: m.return_value.json.return_value = {'id': 1} yield m
这样既避免路径硬编码污染测试逻辑,又能让多个测试复用同一 mock 行为。注意:fixture 必须用
yield
+
with
,否则退出时 patch 不自动还原,可能污染后续测试。 别在
setup_method
或
setUp
里做 patch——pytest fixture 的作用域控制更清晰 fixture 名不要带
mock_
前缀,比如叫
fake_api_response
比
mock_requests_get
更表达意图 如果 patch 的是类的实例方法,确保 patch 的是「使用处」的路径,不是定义处(常见错误:
@patch('module.Class.method')
错,应为
@patch('caller_module.Class.method')
) 什么时候该 mock 外部依赖,什么时候不该? mock 的核心目标是隔离不确定性,不是消灭所有真实调用。数据库、HTTP、文件系统、时间等不可控或慢速依赖必须 mock;但像
json.loads
、
datetime.now()
这类纯函数或轻量操作,mock 反而增加认知负担,且容易因过度隔离导致测试失真。 立即学习 “ Python免费学习笔记(深入) ”; 一个简单判断标准:这个调用是否「可能失败」「耗时不可控」「结果不固定」或「触发副作用(如发邮件)」。如果不是,让它真实运行反而更可靠。 Python 3.14.3 微软官方的 Python 扩展,是 VS Code 安装量最高的扩展(209M+)。集成 IntelliSense(通过 Pylance)、调试(通过 Python Debugger)、代码检查、格式化、重构和单元测试等功能。支持 Jupyter Notebook、虚拟环境管理和多 Python 版本切换。 下载 别 mock
os.path.exists
来测试路径逻辑——改用
tempfile.TemporaryDirectory
创建真实临时目录更直观 mock
time.time()
是合理的,但 mock
math.sqrt()
就毫无意义 对第三方 SDK(如 boto3、redis-py),优先 mock 客户端实例,而不是底层 HTTP 请求——层级太高易断,太低难覆盖
side_effect
和
return_value
怎么选才不翻车?
return_value
适合返回固定数据,
side_effect
才能模拟异常、多态响应或状态变化。但很多人误以为
side_effect=ValueError
就能抛异常——其实它只会把
ValueError
当作返回值返回,真正抛异常得写
side_effect=ValueError()
或
side_effect=lambda: raise ValueError()
。 另一个高频坑:用
side_effect
返回迭代器时,耗尽后再次调用会直接抛
StopIteration
,而不是静默返回
None
。这会让本该只调用一次的函数,在测试中被多调用就崩掉。 抛异常:用
side_effect=ValueError('boom')
,不是
side_effect=ValueError
模拟多次不同返回:用
side_effect=[{'a':1}, {'a':2}, ValueError]
需要动态逻辑(如根据参数返回不同值):用函数,但记得加
*args, **kwargs
签名,否则参数不匹配会静默失败 为什么
autospec=True
不是默认选项,但它值得手动开?
autospec=True
会让 mock 自动继承原对象的签名(参数名、数量、是否可变参),调用时参数错位或多余会立刻报错,而不是静默忽略——这对防止“mock 写错了但测试还绿”的情况特别有用。但它不是默认,是因为会额外反射导入目标对象,可能拖慢测试启动,且对某些动态构造的对象(如部分 SQLAlchemy 模型)会失败。 建议在关键集成测试或团队新项目初期就启用,尤其当你发现 mock 调用看起来没问题,但真实运行却报
TypeError: unexpected keyword argument
时,大概率就是缺
autospec
。 启用方式:所有
patch
和
Mock
构造都加
autospec=True
,例如
mock.patch('foo.bar', autospec=True)
如果遇到
AttributeError: __spec__
,说明目标对象不支持反射,降级为手动定义
spec=SomeClass
它不会帮你校验返回值类型,只管调用签名——别指望它替你 catch 业务逻辑错误 mock 分层这事,本质是平衡「控制力」和「维护成本」。越往上层(比如业务逻辑层)mock,测试越快越稳定;但离真实调用链越远,就越容易漏掉路径差异或副作用。最麻烦的往往不是怎么 mock,而是忘了 mock 后没验证是否真被调用了——别只 assert 返回值,顺手加一句
mock_obj.assert_called_once()
。

相关文章