如何模拟导入
模块 A
在开头包含了 import B
。但是在测试的时候,我想在 A
中“模拟”一下 B
(也就是模拟 A.B
),完全不导入真实的 B
。
实际上,B
在测试环境中是故意没有安装的。
A
是我需要测试的模块。我必须导入 A
及其所有功能。B
是我需要模拟的模块。但是,如果 A
的第一件事就是导入 B
,我该如何在 A
中模拟 B
,并阻止 A
导入真实的 B
呢?
(之所以没有安装 B
,是因为我使用 pypy 来快速测试,而不幸的是 B
还不兼容 pypy。)
这该怎么做呢?
11 个回答
如何模拟一个导入(模拟 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 上不行,而我们每天的测试都是在 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)
然后代码运行的时间就像真实的方法一样。
内置的 __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 中也能正常工作。
你可以在导入 A
之前给 sys.modules['B']
赋值,这样就能达到你想要的效果:
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