例如,我有一些模块( 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())
他们两个被 mock 了:
<MagicMock name='get().content' id='4352254472'>
<MagicMock name='get().content' id='4352254472'>
似乎只打补丁foo.get_ip
方法归因于with patch('foo.requests.get')
,但事实并非如此。
我知道我可以得到 bar.get_fb
调用with
范围,但有些情况下我只是在上下文管理器中运行一个调用许多其他方法的方法,我想修补 requests
仅在一个模块中。
有什么办法可以解决这个问题吗?不改变模块中的导入
最佳答案
这两个位置 foo.requests.get
和 bar.requests.get
指的是同一个对象,所以在一个地方模拟它,你在另一个地方模拟它.
想象一下您将如何实现补丁。您必须找到符号所在的位置并将符号替换为模拟对象。从 with 上下文退出时,您将需要恢复符号的原始值。像(未经测试):
class patch(object):
def __init__(self, symbol):
# separate path to container from name being mocked
parts = symbol.split('.')
self.path = '.'.join(parts[:-1]
self.name = parts[-1]
def __enter__(self):
self.container = ... lookup object referred to by self.path ...
self.save = getattr(self.container, name)
setattr(self.container, name, MagicMock())
def __exit__(self):
setattr(self.container, name, self.save)
所以你的问题是你在请求模块中模拟对象,然后你从 foo 和 bar 引用它。
按照@elethan 的建议,您可以在 foo 中模拟请求模块,甚至在 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
的引用直接拉入它们的 namespace 。
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
主要.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" ...
更新了更完整的解释和更好的解决方案 (2016-10-15)
注意:添加了 wraps=requests.get
以在副作用后调用底层函数。
关于python - 仅在一个模块中修补方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40062073/