如何模拟导入

217 投票
11 回答
116663 浏览
提问于 2025-04-17 09:09

模块 A 在开头包含了 import B。但是在测试的时候,我想在 A 中“模拟”一下 B(也就是模拟 A.B),完全不导入真实的 B

实际上,B 在测试环境中是故意没有安装的。

A 是我需要测试的模块。我必须导入 A 及其所有功能。B 是我需要模拟的模块。但是,如果 A 的第一件事就是导入 B,我该如何在 A 中模拟 B,并阻止 A 导入真实的 B 呢?

(之所以没有安装 B,是因为我使用 pypy 来快速测试,而不幸的是 B 还不兼容 pypy。)

这该怎么做呢?

11 个回答

24

如何模拟一个导入(模拟 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)

然后代码运行的时间就像真实的方法一样。

43

内置的 __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 中也能正常工作。

193

你可以在导入 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

撰写回答