5

例如,我有一些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())

他们是两个嘲笑: <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在一个模块中进行修补。有没有办法解决这个问题?不更改模块中的导入

4

2 回答 2

3

这两个位置引用foo.requests.getbar.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 中模拟 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

酒吧.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后调用底层函数有副作用。

于 2016-10-15T18:13:25.757 回答
1

不要窃取@Neapolitan的风头,但另一种选择是简单地模拟foo.requests而不是foo.requests.get

with patch('foo.requests'):
    print(get_ip())
    print(get_fb())

我认为在您的情况下这两种方法都被嘲笑的原因是,由于requests.get没有显式导入foo.pymock因此必须在模块中查找方法并在requests那里模拟,而不是在requests已经导入的对象中模拟它foo,这样当bar以后导入requests和访问requests.get它时,它会得到模拟版本。但是,如果您patch foo.requests只是修补已导入的模块对象foo,则原始requests模块不会受到影响。

虽然对这个特定问题没有特别的帮助,但这篇文章对于理解patch

于 2016-10-15T18:44:20.120 回答