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