python - 仅在一个模块中修补方法

标签 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

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.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 中模拟请求模块,甚至在 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())

更直接的解决方案是改变您的代码,以便 foobar 将对 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/

相关文章:

python notepad++ 指定参数 sys.argv[1]

python - 如何使用 Raspberry Pi 运行 python 脚本,直到我们手动停止它

Python Quandl 给我错误

python-3.x - 如何有效地检测Python 3中变量的变化?

windows - python unittest的返回状态

python - 如何绘制 pandas 数据框列,其中 x 轴由另外两个列定义,给出起始值和结束值?

python - 在数据框中搜索包含列表中字符串的行

python - 根据日期键的值过滤字典

python - Unittest 子测试中的局部变量未定义

c++ - 组件测试的测试框架