mock 必须 patch 被测模块内实际调用依赖的位置,而非 import 位置;优先用 patch.object 避免路径错误;类实例方法需 patch 引用点;禁用 patch __init__,改用分层 fixture 组装;side_effect 支持多态响应,与 return_value 互斥;必须 assert 调用验证行为。

直接 mock 类本身容易失效,必须 patch 被测代码实际调用的位置,而不是 import 的位置。
mock.patch 目标路径写错是最常见的失败原因
比如被测函数在 app.services.user_client 里调用了 httpx.post,你却去 patch test_user_client.httpx.post 或 httpx.post,那 mock 根本不会生效。
- 正确做法:patch 被测模块内部“访问依赖的地方”,即
@patch('app.services.user_client.httpx.post') - 更安全的替代:用
patch.object(httpx, 'post'),避免字符串路径出错 - 如果依赖是类实例方法(如
UserRepository.find_by_id()),要 patch 实例所在模块的引用点,不是user_repository.UserRepository,而是app.process.user_service.UserRepository
不要 patch __init__,改用 fixture 构造完整依赖链
硬 patch __init__ 会导致实例属性未初始化,后续访问 self.db_conn 或 self.config 直接抛 AttributeError。
- 推荐方式:用 pytest fixture 分层组装——
@pytest.fixture先造mock_db,再造mock_logger,最后组合成user_service - 每个 fixture 职责单一,比如
@pytest.fixture def mock_db(): return Mock(spec=DatabaseConnection) - 需要真实轻量行为时(如内存 SQLite),直接返回真实对象;需要控制返回逻辑时,用
side_effect模拟多态响应
side_effect 是覆盖分支逻辑的关键
return_value 只能固定返回一个值,但真实调用可能成功一次、失败一次、再成功一次。
- 传列表:
mock_api.get.side_effect = [{'id': 1}, None, ValueError('timeout')] - 传函数:
mock_config.get.side_effect = lambda key: 'dev' if key == 'env' else '8080' - 注意:
side_effect和return_value互斥,设了前者,后者自动失效 - 若被 mock 的是构造函数(如
SomeClient(...)),要 patch 返回的实例方法,例如mock_client.return_value.fetch_data.side_effect = [...]
必须验证调用,否则 mock 就是摆设
只 mock 不 assert,等于没测——参数拼错、少调一次、根本没进分支,全都会漏掉。
- 基础验证:
mock_api.post.assert_called_once_with(url='https://api.example.com', json={'x': 1}) - 检查多次调用:
from unittest.mock import call; mock_api.post.assert_has_calls([call(...), call(...)]) - 确认没调用:
mock_db.save.assert_not_called() - 别忘了:所有
call都得从unittest.mock导入,不是内置
复杂点在于依赖嵌套层级深时,patch 路径容易错一层、fixture 组合顺序搞反、side_effect 返回值和实际调用次数不匹配——这些地方一错,测试就变成“看起来过了,其实没跑通”。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











