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免费学习笔记(深入)”;
一个简单判断标准:这个调用是否「可能失败」「耗时不可控」「结果不固定」或「触发副作用(如发邮件)」。如果不是,让它真实运行反而更可靠。
- 别 mock
os.path.exists来测试路径逻辑——改用tempfile.TemporaryDirectory创建真实临时目录更直观 - mock
time.time()是合理的,但 mockmath.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_obj.assert_called_once()。










