问题:如何模拟导入
模块A
包括import B
在其顶部。然而在试验条件下,我想嘲笑 B
的A
(模拟A.B
)和进口完全避免B
。
实际上,B
并不是故意在测试环境中安装的。
A
是被测单元。我必须导入A
所有功能。B
是我需要模拟的模块。但是,如果第一件事就是导入,我该如何B
在其中模拟A
并停止A
导入实数?B
A
B
(未安装B的原因是我使用pypy进行了快速测试,但不幸的是B尚未与pypy兼容。)
怎么办呢?
Module A
includes import B
at its top. However under test conditions I’d like to mock B
in A
(mock A.B
) and completely refrain from importing B
.
In fact, B
isn’t installed in the test environment on purpose.
A
is the unit under test. I have to import A
with all its functionality. B
is the module I need to mock. But how can I mock B
within A
and stop A
from importing the real B
, if the first thing A
does is import B
?
(The reason B isn’t installed is that I use pypy for quick testing and unfortunately B isn’t compatible with pypy yet.)
How could this be done?
回答 0
您可以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__)
print mock_B
。您仍然必须mock_B.py
在模拟B
实际功能/变量/等的地方创建一个。或者,您可以直接分配一个Mock()
:
test.py:
import sys
sys.modules['B'] = Mock()
import A
You can assign to sys.modules['B']
before importing A
to get what you want:
test.py:
import sys
sys.modules['B'] = __import__('mock_B')
import A
print(A.B.__name__)
A.py:
import B
Note B.py does not exist, but when running test.py
no error is returned and print(A.B.__name__)
prints mock_B
. You still have to create a mock_B.py
where you mock B
‘s actual functions/variables/etc. Or you can just assign a Mock()
directly:
test.py:
import sys
sys.modules['B'] = Mock()
import A
回答 1
__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'
Python 3的注意事项:
如3.0的变更日志所述,__builtin__
现名为builtins
:
将模块重命名__builtin__
为builtins
(删除下划线,添加“ s”)。
如果更换这个答案的代码工作正常__builtin__
通过builtins
为Python 3。
The builtin __import__
can be mocked with the ‘mock’ library for more control:
# 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
Say A
looks like:
import B
def a():
return B.func()
A.a()
returns b_mock.func()
which can be mocked also.
b_mock.func.return_value = 'spam'
A.a() # returns 'spam'
Note for Python 3:
As stated in the changelog for 3.0, __builtin__
is now named builtins
:
Renamed module __builtin__
to builtins
(removing the underscores, adding an ‘s’).
The code in this answer works fine if you replace __builtin__
by builtins
for Python 3.
回答 2
如何模拟导入(模拟AB)?
模块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)
然后,就像真实方法一样,代码需要一些时间才能运行。
How to mock an import, (mock A.B)?
Module A includes import B at its top.
Easy, just mock the library in sys.modules before it gets imported:
if wrong_platform():
sys.modules['B'] = mock.MagicMock()
and then, so long as A
doesn’t rely on specific types of data being returned from B’s objects:
import A
should just work.
You can also mock import A.B
:
This works even if you have submodules, but you’ll want to mock each module. Say you have this:
from foo import This, That, andTheOtherThing
from foo.bar import Yada, YadaYada
from foo.baz import Blah, getBlah, boink
To mock, simply do the below before the module that contains the above is imported:
sys.modules['foo'] = MagicMock()
sys.modules['foo.bar'] = MagicMock()
sys.modules['foo.baz'] = MagicMock()
(My experience: I had a dependency that works on one platform, Windows, but didn’t work on Linux, where we run our daily tests.
So I needed to mock the dependency for our tests. Luckily it was a black box, so I didn’t need to set up a lot of interaction.)
Mocking Side Effects
Addendum: Actually, I needed to simulate a side-effect that took some time. So I needed an object’s method to sleep for a second. That would work like this:
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)
And then the code takes some time to run, just like the real method.
回答 3
我意识到我在这里参加聚会有点晚了,但这是一种通过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')
I realize I’m a bit late to the party here, but here’s a somewhat insane way to automate this with the mock
library:
(here’s an example usage)
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
The reason this is so ridiculously complicated is when an import occurs python basically does this (take for example from herp.derp import foo
)
- Does
sys.modules['herp']
exist? Else import it. If still not ImportError
- Does
sys.modules['herp.derp']
exist? Else import it. If still not ImportError
- Get attribute
foo
of sys.modules['herp.derp']
. Else ImportError
foo = sys.modules['herp.derp'].foo
There are some downsides to this hacked together solution: If something else relies on other stuff in the module path this kind of screws it over. Also this only works for stuff that is being imported inline such as
def foo():
import herp.derp
or
def foo():
__import__('herp.derp')
回答 4
亚伦·霍尔的答案对我有用。只想提一件事,
如果A.py
你愿意
from B.C.D import E
然后test.py
您必须模拟路径中的每个模块,否则会得到ImportError
sys.modules['B'] = mock.MagicMock()
sys.modules['B.C'] = mock.MagicMock()
sys.modules['B.C.D'] = mock.MagicMock()
Aaron Hall’s answer works for me.
Just want to mention one important thing,
if in A.py
you do
from B.C.D import E
then in test.py
you must mock every module along the path, otherwise you get ImportError
sys.modules['B'] = mock.MagicMock()
sys.modules['B.C'] = mock.MagicMock()
sys.modules['B.C.D'] = mock.MagicMock()
回答 5
我找到了在Python中模拟导入的好方法。这是Eric的Zaadi解决方案,我在Django应用程序中使用了该解决方案。
我有一个类SeatInterface
,它是Seat
模型类的接口。所以在我的seat_interface
模块中,我有这样一个导入:
from ..models import Seat
class SeatInterface(object):
(...)
我想为SeatInterface
模拟Seat
类创建隔离测试FakeSeat
。问题是-在Django应用程序关闭的情况下,tu运行离线测试的方式。我有以下错误:
配置不正确:请求的设置为BASE_DIR,但未配置设置。您必须先定义环境变量DJANGO_SETTINGS_MODULE或调用settings.configure()才能访问设置。
在0.078秒内进行了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.002秒内进行1次测试
好
I found fine way to mock the imports in Python. It’s Eric’s Zaadi solution found here which I just use inside my Django application.
I’ve got class SeatInterface
which is interface to Seat
model class.
So inside my seat_interface
module I have such an import:
from ..models import Seat
class SeatInterface(object):
(...)
I wanted to create isolated tests for SeatInterface
class with mocked Seat
class as FakeSeat
. The problem was – how tu run tests offline, where Django application is down. I had below error:
ImproperlyConfigured: Requested setting BASE_DIR, but settings are not
configured. You must either define the environment variable
DJANGO_SETTINGS_MODULE or call settings.configure() before accessing
settings.
Ran 1 test in 0.078s
FAILED (errors=1)
The solution was:
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
And then test magically runs OK :)
.
Ran 1 test in 0.002s
OK
回答 6
如果这样做,import ModuleB
您实际上是在__import__
按以下方式调用内置方法:
ModuleB = __import__('ModuleB', globals(), locals(), [], -1)
您可以通过导入__builtin__
模块并对该方法进行包装来覆盖此__builtin__.__import__
方法。或者,您可以使用模块中的NullImporter
挂钩imp
。捕获异常并在except
-block中模拟您的模块/类。
指向相关文档的指针:
docs.python.org: __import__
使用imp模块访问Import内部
我希望这有帮助。是HIGHLY劝你踏进Python编程的更神秘的周边和一)扎实的了解,你真正要实现和B)的影响的深入理解什么是重要的。
If you do an import ModuleB
you are really calling the builtin method __import__
as:
ModuleB = __import__('ModuleB', globals(), locals(), [], -1)
You could overwrite this method by importing the __builtin__
module and make a wrapper around the __builtin__.__import__
method. Or you could play with the NullImporter
hook from the imp
module. Catching the exception and Mock your module/class in the except
-block.
Pointer to the relevant docs:
docs.python.org: __import__
Accessing Import internals with the imp Module
I hope this helps. Be HIGHLY adviced that you step into the more arcane perimeters of python programming and that a) solid understanding what you really want to achieve and b)thorough understanding of the implications is important.