如何在不知道 python 中的模块名称的情况下模拟被测模块直接导入的函数
How to mock a function imported directly by the tested module without knowing the module name in python
假设我在模块中定义了一个函数:
module_a.py
def foo():
return 10
我想创建一个 API 来修补函数:
patcher.py
import mock
class Patcher(object):
def __enter__(self):
self.patcher = mock.patch('module_a.foo',
mock.Mock(return_value=15))
self.patcher.start()
def __exit__(self, *args):
self.patcher.stop()
问题是,我不知道将使用我的 API 的模块的名称是什么。所以测试看起来像这样:
test1.py
from patcher import Patcher
import module_a
with Patcher():
assert module_a.foo() == 15
会起作用。但是这样写的测试:
test2.py
from patcher import Patcher
from module_a import foo
with Patcher():
assert foo() == 15
会失败。
有没有像第一个选项那样让 API 用户编写它的测试和模块(!)?
有一种方法可以 "patch" 在不知道补丁发生在哪里的情况下覆盖函数。这是我的问题的要求,因为 patcher
是我的库 API,并且我不想得到使用我的库的每个测试模块的路径。
我找到的解决方案是传递所有加载的模块并尝试在其中找到 foo
,然后更改它 - 有点像我自己实现补丁。如果仅在 Patcher
启动后才会导入,我自己加载了模块,并对其进行了更改。
现在代码将如下所示:
补丁程序
import sys
import mock
from module_a import foo as _orig_foo
import module_a
class Patcher(object):
def __init__(self):
self.undo_set = set()
self.fake_foo = mock.Mock(return_value=15)
def __enter__(self):
modules = [
module for mod_name, module in sys.modules.items() if
mod_name is not None and module is not None and
hasattr(module, '__name__') and
module.__name__ not in ('module_a', 'patcher')
]
for module in modules:
for attr in dir(module):
try:
attribute_value = getattr(module, attr)
except (ValueError, AttributeError, ImportError):
# For some libraries, this happen.
continue
if id(attribute_value) == id(_orig_foo):
setattr(module, attr, self.fake_foo)
self.undo_set.add((module, attr, attribute_value))
# Solve for future imports
module_a.foo = self.fake_foo
def __exit__(self, *args):
module_a.foo = _orig_foo
for mod, attr, val in self.undo_set:
setattr(mod, attr, val)
self.undo_set = set()
假设我在模块中定义了一个函数:
module_a.py
def foo():
return 10
我想创建一个 API 来修补函数:
patcher.py
import mock
class Patcher(object):
def __enter__(self):
self.patcher = mock.patch('module_a.foo',
mock.Mock(return_value=15))
self.patcher.start()
def __exit__(self, *args):
self.patcher.stop()
问题是,我不知道将使用我的 API 的模块的名称是什么。所以测试看起来像这样:
test1.py
from patcher import Patcher
import module_a
with Patcher():
assert module_a.foo() == 15
会起作用。但是这样写的测试:
test2.py
from patcher import Patcher
from module_a import foo
with Patcher():
assert foo() == 15
会失败。
有没有像第一个选项那样让 API 用户编写它的测试和模块(!)?
有一种方法可以 "patch" 在不知道补丁发生在哪里的情况下覆盖函数。这是我的问题的要求,因为 patcher
是我的库 API,并且我不想得到使用我的库的每个测试模块的路径。
我找到的解决方案是传递所有加载的模块并尝试在其中找到 foo
,然后更改它 - 有点像我自己实现补丁。如果仅在 Patcher
启动后才会导入,我自己加载了模块,并对其进行了更改。
现在代码将如下所示:
补丁程序
import sys
import mock
from module_a import foo as _orig_foo
import module_a
class Patcher(object):
def __init__(self):
self.undo_set = set()
self.fake_foo = mock.Mock(return_value=15)
def __enter__(self):
modules = [
module for mod_name, module in sys.modules.items() if
mod_name is not None and module is not None and
hasattr(module, '__name__') and
module.__name__ not in ('module_a', 'patcher')
]
for module in modules:
for attr in dir(module):
try:
attribute_value = getattr(module, attr)
except (ValueError, AttributeError, ImportError):
# For some libraries, this happen.
continue
if id(attribute_value) == id(_orig_foo):
setattr(module, attr, self.fake_foo)
self.undo_set.add((module, attr, attribute_value))
# Solve for future imports
module_a.foo = self.fake_foo
def __exit__(self, *args):
module_a.foo = _orig_foo
for mod, attr, val in self.undo_set:
setattr(mod, attr, val)
self.undo_set = set()