模块A
在其顶部包含import B
。但是,在测试条件下,我希望在B
(模拟A
)中mock A.B
并完全避免导入B
。
事实上,B
并未故意安装在测试环境中。
A
是被测单位。我必须导入A
及其所有功能。 B
是我需要模拟的模块。但是,如果B
做的第一件事是导入A
,我如何在A
内模仿B
并停止A
导入真实B
?
(没有安装B的原因是我使用pypy进行快速测试,不幸的是B还与pypy兼容。)
怎么可以这样做?
答案 0 :(得分:109)
您可以在导入sys.modules['B']
之前分配到A
以获得您想要的内容:
<强> test.py 强>:
import sys
sys.modules['B'] = __import__('mock_B')
import A
print(A.B.__name__)
<强> A.py 强>:
import B
注意B.py不存在,但在运行test.py
时,不会返回任何错误,print(A.B.__name__)
会打印mock_B
。你仍然需要创建一个mock_B.py
来模拟B的实际函数/变量/等。或者您可以直接指定Mock():
<强> test.py 强>:
import sys
sys.modules['B'] = Mock()
import A
答案 1 :(得分:17)
内置__import__
可以使用'mock'库进行模拟以获得更多控制权:
# Store original __import__
orig_import = __import__
# This will be the B module
b_mock = mock.Mock()
def import_mock(name, *args):
if name == 'B':
return b_mock
return orig_import(name, *args)
with mock.patch('__builtin__.__import__', side_effect=import_mock):
import A
说A
看起来像:
import B
def a():
return B.func()
A.a()
会返回b_mock.func()
,也可以嘲笑。
b_mock.func.return_value = 'spam'
A.a() # returns 'spam'
答案 2 :(得分:12)
如何模拟导入,(模拟A.B)?
模块A在其顶部包含导入B.
很简单,只需在导入之前模拟sys.modules中的库:
if wrong_platform():
sys.modules['B'] = mock.MagicMock()
然后,只要A
不依赖于从B对象返回的特定类型的数据:
import A
应该正常工作。
import A.B
:即使您有子模块,这也有效,但您想要模拟每个模块。说你有这个:
from foo import This, That, andTheOtherThing
from foo.bar import Yada, YadaYada
from foo.baz import Blah, getBlah, boink
要进行模拟,只需在导入包含上述模块的模块之前执行以下操作:
sys.modules['foo'] = MagicMock()
sys.modules['foo.bar'] = MagicMock()
sys.modules['foo.baz'] = MagicMock()
(我的经验:我有一个依赖,适用于一个平台,Windows,但没有在Linux上工作,我们运行我们的日常测试。 所以我需要模拟测试的依赖性。幸运的是它是一个黑盒子,所以我不需要建立很多互动。)
附录:实际上,我需要模拟需要一段时间的副作用。所以我需要一个物体的方法来睡一秒钟。这将是这样的:
sys.modules['foo'] = MagicMock()
sys.modules['foo.bar'] = MagicMock()
sys.modules['foo.baz'] = MagicMock()
# setup the side-effect:
from time import sleep
def sleep_one(*args):
sleep(1)
# this gives us the mock objects that will be used
from foo.bar import MyObject
my_instance = MyObject()
# mock the method!
my_instance.method_that_takes_time = mock.MagicMock(side_effect=sleep_one)
然后代码需要一些时间来运行,就像真正的方法一样。
答案 3 :(得分:7)
我意识到我在这里参加聚会有点晚了,但是使用mock
库自动执行此操作有点疯狂:
(这是一个示例用法)
import contextlib
import collections
import mock
import sys
def fake_module(**args):
return (collections.namedtuple('module', args.keys())(**args))
def get_patch_dict(dotted_module_path, module):
patch_dict = {}
module_splits = dotted_module_path.split('.')
# Add our module to the patch dict
patch_dict[dotted_module_path] = module
# We add the rest of the fake modules in backwards
while module_splits:
# This adds the next level up into the patch dict which is a fake
# module that points at the next level down
patch_dict['.'.join(module_splits[:-1])] = fake_module(
**{module_splits[-1]: patch_dict['.'.join(module_splits)]}
)
module_splits = module_splits[:-1]
return patch_dict
with mock.patch.dict(
sys.modules,
get_patch_dict('herp.derp', fake_module(foo='bar'))
):
import herp.derp
# prints bar
print herp.derp.foo
这是如此荒谬复杂的原因是当导入发生时python基本上这样做(例如from herp.derp import foo
)
sys.modules['herp']
是否存在?否则导入它。如果还不是ImportError
sys.modules['herp.derp']
是否存在?否则导入它。如果还不是ImportError
foo
的属性sys.modules['herp.derp']
。其他ImportError
foo = sys.modules['herp.derp'].foo
这个被黑客入侵的解决方案有一些缺点:如果其他东西依赖于模块路径中的其他东西,那么这种方法就会将其搞砸。此仅适用于内联导入的内容,例如
def foo():
import herp.derp
或
def foo():
__import__('herp.derp')
答案 4 :(得分:3)
如果您执行import ModuleB
,则实际上将内置方法__import__
称为:
ModuleB = __import__('ModuleB', globals(), locals(), [], -1)
您可以通过导入__builtin__
模块覆盖此方法,并围绕__builtin__.__import__
方法创建一个包装器。或者您可以使用NullImporter
模块中的imp
挂钩。捕获异常并在except
- 块中模拟您的模块/类。
指向相关文档的指针:
Accessing Import internals with the imp Module
我希望这会有所帮助。 HIGHLY 建议您进入更为神秘的python编程周边,并且a)充分了解您真正想要达到的目标,并且b)彻底了解其含义非常重要。
答案 5 :(得分:3)
我找到了在Python中模拟导入的好方法。发现{strong> Eric的Zaadi 解决方案here,我只是在 Django 应用程序中使用。
我有一个SeatInterface
类,它是Seat
模型类的接口。
所以在我的seat_interface
模块中我有这样的导入:
from ..models import Seat
class SeatInterface(object):
(...)
我想为SeatInterface
类创建隔离的测试,其中模拟的Seat
类为FakeSeat
。问题是 - 如何脱机测试运行,Django应用程序停机的地方。我有以下错误:
NotperlyConfigured:请求设置BASE_DIR,但设置不是 配置。您必须定义环境变量 DJANGO_SETTINGS_MODULE或在访问之前调用settings.configure() 设置。
在0.078s中进行1次测试
失败(错误= 1)
解决方案是:
import unittest
from mock import MagicMock, patch
class FakeSeat(object):
pass
class TestSeatInterface(unittest.TestCase):
def setUp(self):
models_mock = MagicMock()
models_mock.Seat.return_value = FakeSeat
modules = {'app.app.models': models_mock}
patch.dict('sys.modules', modules).start()
def test1(self):
from app.app.models_interface.seat_interface import SeatInterface
然后测试神奇地运行OK:)
。
以0.002s进行1次测试行
答案 6 :(得分:1)
亚伦·霍尔的答案对我有用。 只想提一件事,
如果您在A.py
中这样做
from B.C.D import E
然后在test.py
中,必须模拟路径中的每个模块,否则得到ImportError
sys.moduels['B'] = mock.MagicMock()
sys.moduels['B.C'] = mock.MagicMock()
sys.moduels['B.C.D'] = mock.MagicMock()
答案 7 :(得分:0)
我知道这是一个相当老的问题,但是最近我发现自己又回到了这个问题上,并且想分享一个简洁的解决方案。
import sys
from unittest import mock
def mock_module_import(module):
def _outer_wrapper(func):
def _inner_wrapper(*args, **kwargs):
orig = sys.modules.get(module) # get the original module, if present
sys.modules[module] = mock.MagicMock() # patch it
try:
return func(*args, **kwargs)
finally:
if orig is not None: # if the module was installed, restore patch
sys.modules[module] = orig
else: # if the module never existed, remove the key
del sys.modules[module]
return _inner_wrapper
return _outer_wrapper
它的工作原理是临时修补sys.modules
中模块的密钥,然后在调用修饰函数后恢复原始模块。可以将其用于可能未在测试环境中安装软件包的场景,也可以用于更为复杂的场景,在该场景中,修补的模块实际上可能会执行其自己的内部猴子修补(这就是我所面对的情况)。>
这是一个使用示例:
@mock_module_import("some_module")
def test_foo():
assert True