patch 装饰器必须作用于被测函数中目标函数的「导入位置」而非定义位置,如 service.py 中 from utils import fetch_data,则应 @patch('service.fetch_data');否则 patch 不生效,导致调用原函数。

patch 装饰器怎么写才生效
patch 不是写了就自动替换,它必须作用在被测试函数的「导入位置」,不是定义位置。比如你在 utils.py 里定义了 fetch_data(),但在 service.py 里写了 from utils import fetch_data,那 patch 就得 mock service.fetch_data,而不是 utils.fetch_data。
常见错误现象:AssertionError: Expected call: mock(123), Actual call: fetch_data(123) —— 这说明 patch 根本没生效,调用的还是原函数。
- 装饰器写法:在测试函数上加
@patch('service.fetch_data'),注意路径是「被使用的地方」 - 上下文管理器写法:在测试函数体内用
with patch('service.fetch_data') as mock_fetch: - 如果 patch 类,要 mock 类的实例方法,通常得 patch 实例的
method,而不是类方法本身(除非是 @staticmethod)
mock.return_value 和 mock.side_effect 怎么选
前者返回固定值,后者可以抛异常、返回不同值、甚至执行真实逻辑。别一上来就用 return_value,容易掩盖副作用逻辑。
使用场景:return_value 适合纯数据读取类函数(如查缓存、读配置);side_effect 更贴近真实行为,比如第一次调用返回数据,第二次抛 ConnectionError。
立即学习“Python免费学习笔记(深入)”;
-
mock.return_value = {'id': 1}→ 每次调用都返回这个字典 -
mock.side_effect = [{'id': 1}, {'id': 2}, ValueError('timeout')]→ 三次调用分别返回不同结果 -
mock.side_effect = lambda x: x * 2→ 接收参数并动态计算
patch.object 和 patch.path 的区别在哪
patch.object 是按对象引用 patch,更安全;patch(即 patch.path)是按字符串路径 patch,更灵活但易错。
性能/兼容性影响:两者底层一样,但 patch.object 在 refactoring 时更稳定——改了模块名或导入方式,patch.object 不会崩,而字符串路径会。
- 推荐用
@patch.object(utils, 'fetch_data'),比@patch('myapp.utils.fetch_data')更直觉、更少拼错 - 但如果要 patch 内置函数(如
builtins.open)或第三方模块中未显式导入的对象,只能用字符串路径 - 注意:
patch.object的第一个参数必须是已导入的对象,不能是字符串
为什么 patch 后单元测试通过,集成时却失败
因为 patch 只拦截了单元测试里的调用链,没动真实依赖。常见于:数据库连接、HTTP 请求、文件 I/O 等外部交互没被完整覆盖,或者 patch 位置漏了某一层导入。
容易踩的坑:patch 默认只 mock 当前作用域,如果被测函数内部又动态导入了模块(比如用 importlib.import_module),patch 就失效了;还有异步函数里用 asyncio.create_task 启的新协程,不会被同步 patch 捕获。
- 检查是否所有调用路径都被 patch:搜代码里所有对目标函数的引用,确认每个导入点都 patch 到了
- 避免 patch 太“深”:比如 patch
requests.Session.get,不如 patchrequests.get(更贴近使用层) - 测试后记得清理:用
mock.reset_mock()或确保每个 test 方法独立,否则 mock 状态可能污染下一个测试
最麻烦的是 patch 对象的生命周期和作用域重叠,比如多个测试共用一个 mock 实例,返回值或调用记录串了——这种问题不报错,但结果不可靠。










