patch 必须作用于被调用位置而非定义位置,如 utils.fetch_data 调用 requests.get 则应 patch('utils.requests.get');return_value 与 side_effect 不可混用,后者优先;patch.object 需传入真实对象而非字符串;异步函数必须用 asyncmock。

patch 作用域没管好,mock 对象根本没生效
最常见的问题是 patch 没覆盖到「实际被调用的位置」,而不是「定义位置」。比如模块 utils.py 里有函数 fetch_data(),它内部调用了 requests.get();你却在测试文件里写 @patch('requests.get') —— 这通常无效,因为 utils.fetch_data 实际导入并使用的是它自己命名空间里的 requests,得 patch 它被引用的地方:@patch('utils.requests.get')。
实操建议:
- 查清目标函数在哪被 import:打开被测模块源码,看它是
import requests还是from requests import get,再决定 patch 路径 - 优先用装饰器 patch 目标模块的命名空间,例如
@patch('myapp.services.api_client.requests.post') - 临时 patch 可用上下文管理器:
with patch('utils.requests.get') as mock_get: mock_get.return_value.status_code = 200 result = utils.fetch_data() - 避免 patch 内置模块如
open时写成@patch('__builtin__.open')(Python 2)或@patch('builtins.open')(Python 3),版本错就直接报AttributeError
return_value 和 side_effect 混用导致行为不可控
return_value 是静态返回,side_effect 支持抛异常、返回可迭代对象、或调用真实函数。但很多人把两者同时设,结果 side_effect 会完全接管,return_value 被忽略——这不是 bug,是设计如此。
实操建议:
- 要模拟多次调用不同响应,用
side_effect=[res1, res2, Exception('timeout')] - 要复用真实函数逻辑(比如只 mock 网络部分,但保留 JSON 解析),设
side_effect=real_function - 别写
mock_obj.return_value = 'ok'; mock_obj.side_effect = ValueError,后者生效,前者白设 - 检查 mock 是否被调用过:断言
mock_obj.assert_called_once()或mock_obj.assert_called_with(url='https://api.com')
patch.object 适合打补丁类方法,但 target 容易写错
patch.object 的第一个参数必须是「活的对象」,不是字符串路径。比如你想 mock 类 ApiClient 的实例方法 send(),不能写 patch.object('myapp.ApiClient', 'send'),而要先 import 类:from myapp import ApiClient,再写 patch.object(ApiClient, 'send')。
实操建议:
- 对类方法 patch:用
patch.object(TargetClass, 'method_name'),不是字符串类名 - 对实例属性 patch(比如 patch 某个对象的
session属性):传入具体实例,patch.object(instance, 'session') - 若目标方法是 @property,一样能 patch,但记得 mock 的是属性名,不是调用形式:
type(mock_instance).token = PropertyMock(return_value='abc123') - 注意 patch.object 不会自动启动,需搭配
start()或用作装饰器/上下文管理器
异步函数用 patch 得配合 AsyncMock,否则 await 报错
Python 3.8+ 的 AsyncMock 是专为 async def 设计的。直接用普通 Mock 去 patch 异步方法,调用时会报 TypeError: object Mock can't be used in 'await' expression。
实操建议:
- mock 异步函数必须用
AsyncMock:mock_fetch = AsyncMock(return_value={'id': 1}) with patch('myapp.db.async_fetch', new=mock_fetch): result = await myapp.process_user(123) - 如果用
@patch装饰器,加new_callable=AsyncMock参数 -
side_effect也支持协程:side_effect=AsyncMock(side_effect=[ValueError, {'ok': True}]) - 别忘了 await 断言:用
await mock_obj.assert_awaited_once()替代assert_called_once()
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











