为何@patch.dict生效,@patch.object却未成功打补丁requests?
问题:os.environ补丁生效但requests.get补丁失效的原因
代码场景
业务代码文件dummy.py:
# dummy.py import os from requests import get def my_func(): name = os.environ.get('name') response = get('http://www.google.com') return name, response.content
测试代码文件:
import unittest import os import requests from ddt import ddt, data, unpack from mock import patch, Mock, MagicMock from dummy import my_func mock_response = Mock() mock_response.content = "...content" @ddt @patch.dict(os.environ, {"name": "patched"}) @patch.object(requests, requests.get.__name__, return_value=mock_response) class TestDummy(unittest.TestCase): def test_dummy(self, mock_get): name, content = my_func() print("name: {}".format(name)) print("content: {}".format(content))
用户疑问:原本认为两行装饰器行为类似,但运行测试时os.environ补丁生效,requests.get却未被补丁,原因是什么?
核心原因:导入绑定的差异
这两个补丁的本质差异在于业务代码中对目标对象的引用方式:
- 对于
os.environ:业务代码里通过os.environ.get调用,每次都会直接访问os模块中的environ字典对象,所以patch.dict修改os.environ后,后续调用能直接拿到修改后的值。 - 对于
requests.get:业务代码里用了from requests import get,这行代码会把requests模块中的get函数直接复制一份引用到dummy模块的命名空间里。后续my_func调用的get,是dummy模块里的这份本地引用,而不是requests模块里的原函数。测试补丁的是requests模块里的get,自然不会影响到dummy模块里已经绑定好的引用。
两种修复方案
方案1:修改业务代码的导入方式
把dummy.py改成导入整个requests模块,调用时通过模块访问get:
# dummy.py import os import requests def my_func(): name = os.environ.get('name') response = requests.get('http://www.google.com') return name, response.content
这样每次调用requests.get都会从requests模块获取函数,测试里的patch.object(requests, 'get', ...)就能生效。
方案2:直接补丁业务模块里的引用
如果不想修改业务代码,就直接补丁dummy模块里的get引用:
# 测试文件中的装饰器替换为 @patch.object(dummy, 'get', return_value=mock_response)
因为dummy模块里的get是my_func实际调用的对象,补丁它就能替换掉原始的get函数。
内容的提问来源于stack exchange,提问作者user1187968
相关产品推荐
相关产品推荐

