Python 仅在一个模块中使用修补方法
例如,我有一些模块(Python 仅在一个模块中使用修补方法,python,python-3.x,python-unittest,python-mock,python-unittest.mock,Python,Python 3.x,Python Unittest,Python Mock,Python Unittest.mock,例如,我有一些模块(foo.py)和下一个代码: import requests def get_ip(): return requests.get('http://jsonip.com/').content 模块bar.py具有类似代码: import requests def get_fb(): return requests.get('https://fb.com/').content 我只是不明白为什么接下来会发生: from mock import patch
foo.py
)和下一个代码:
import requests
def get_ip():
return requests.get('http://jsonip.com/').content
模块bar.py
具有类似代码:
import requests
def get_fb():
return requests.get('https://fb.com/').content
我只是不明白为什么接下来会发生:
from mock import patch
from foo import get_ip
from bar import get_fb
with patch('foo.requests.get'):
print(get_ip())
print(get_fb())
他们是两个被嘲笑的人:
由于使用补丁('foo.requests.get')的,它似乎只修补foo.get\u ip
方法,但事实并非如此。
我知道我可以通过
作用域从调用,但在某些情况下,我只是在上下文管理器中运行一个调用许多其他方法的方法,我只想在一个模块中修补请求
。
有什么办法解决这个问题吗?在不更改模块中的导入的情况下,两个位置foo.requests.get
和bar.requests.get
引用同一个对象,因此在一个位置模拟它,在另一个位置模拟它
想象一下您可能如何实现补丁。您必须找到符号的位置,并用模拟对象替换符号。退出with上下文时,需要恢复符号的原始值。类似于(未经测试):
因此,您的问题是,您正在模拟请求模块中的对象,然后您将从foo和bar引用该对象
按照@elethan的建议,您可以在foo中模拟requests模块,甚至在get方法上提供副作用:
from unittest import mock
import requests
from foo import get_ip
from bar import get_fb
def fake_get(*args, **kw):
print("calling get with", args, kw)
return mock.DEFAULT
replacement = mock.MagicMock(requests)
replacement.get = mock.Mock(requests.get, side_effect=fake_get, wraps=requests.get)
with mock.patch('foo.requests', new=replacement):
print(get_ip())
print(get_fb())
一个更直接的解决方案是改变代码,以便foo
和bar
将对get
的引用直接拉到它们的名称空间中
foo.py:
from requests import get
def get_ip():
return get('http://jsonip.com/').content
bar.py:
from requests import get
def get_ip():
return get('https://fb.com/').content
main.py:
from mock import patch
from foo import get_ip
from bar import get_fb
with patch('foo.get'):
print(get_ip())
print(get_fb())
制作:
<MagicMock name='get().content' id='4350500992'>
b'<!DOCTYPE html>\n<html lang="en" id="facebook" ...
b'\n不要偷那不勒斯人的雷霆,但另一种选择是简单地模仿foo.requests
,而不是foo.requests.get
:
with patch('foo.requests'):
print(get_ip())
print(get_fb())
我认为,在您的案例中,这两种方法都会被模拟的原因是,由于requests.get
没有在foo.py
中显式导入,mock
必须在requests
模块中查找该方法并在那里进行模拟,而不是在已经导入到foo
的requests
对象中模拟它,这样当bar
以后导入requests
并访问requests.get
时,它将获得模拟版本。但是,如果您改为修补
foo.requests
,则只需修补已导入到foo
中的模块对象,原始的请求
模块将不会受到影响
虽然对这个特定问题没有特别的帮助,但对于理解补丁的微妙之处非常有用,据我所知,保持foo.py和bar.py不变,并模仿foo.requests
而不是foo.requests.get
谢谢,但我也知道这个解决方案,并在帖子中提到,不改变模块中的导入。我希望有一个解决方案,不需要改变脚本imports@elethan这很有效,thnx;)但是为什么在模拟foo.requests
时,它只模拟foo.py
中的请求
,而在模拟foo.requests.get
时,它模拟foo.py
和bar.py
。你能写出一个完整的答案吗?@hasam我认为这是因为当你模拟foo.requests
时,它模拟的是已经导入foo
的模块对象,因此不会影响将导入bar
的模块对象。但是,当您模拟foo.requests.get
时,它将查找在foo
中导入的requests
对象,然后在原始模块中查找get
,并对其进行模拟,因此当bar
导入requests
时,它将获得模拟的get
方法。这有意义吗?@elethan是的,谢谢。我想你应该给我的问题写一个答案,我会接受的,因为这正是我所需要的。有任何方法可以对请求产生副作用。用这种方法获得方法吗?@hasam是的,只要你在你的测试模块中导入foo
,你应该能够做foo.requests.get.side\u effect=无论什么副作用
,因为foo.requests.get
将是一个Mock
对象。我认为使用装饰程序来修补你的函数应该可以做到这一点。
with patch('foo.requests'):
print(get_ip())
print(get_fb())