如何模拟导入


143

模块A包括import B在其顶部。然而在试验条件下,我想嘲笑 BA(模拟A.B)和进口完全避免B

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

A是被测单元。我必须导入A所有功能。B是我需要模拟的模块。但是,如果第一件事就是导入,我该如何B在其中模拟A并停止A导入实数?BAB

(未安装B的原因是我使用pypy进行了快速测试,但不幸的是B尚未与pypy兼容。)

怎么办呢?

Answers:


134

您可以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

3
请记住,它Mock不会修补某些魔术属性(__%s____name__
reclosedev

7
@reclosedev -有魔术模拟
乔纳森

2
您如何撤消此操作,以便B导入再次成为ImportError?我尝试过,sys.modules['B'] = None但似乎没有用。
audiodude19年

2
在测试结束时,如何重置此模拟导入,以使其他单元测试文件不受模拟对象的影响?
Riya John

1
为了清楚起见,您应该编辑答案以实际导入mock,然后致电mock.Mock()
nmz787

28

__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。


1
有没有人确认这项工作?我看到有人为import_mock调用get import A,但没有为其导入的任何东西打电话。
乔纳森·莱因哈特

3
使用Python 3.4.3,我得到ImportError: No module named '__builtin__'
Lucas Cimon

您必须输入__builtin__
Aidenhjj

1
@LucasCimon,取代__builtin__builtins用于python3(docs.python.org/3/whatsnew/3.0.html?highlight=__builtin__
卢克马林

17

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

然后,就像真实方法一样,代码需要一些时间才能运行。


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

  1. 是否sys.modules['herp']存在?否则导入。如果还没有ImportError
  2. 是否sys.modules['herp.derp']存在?否则导入。如果还没有ImportError
  3. 获取属性foosys.modules['herp.derp']。其他ImportError
  4. foo = sys.modules['herp.derp'].foo

这种被黑客入侵的解决方案有一些缺点:如果模块路径中的其他内容依赖于其他内容,则将其拧紧。而且,这适用于内联导入的内容,例如

def foo():
    import herp.derp

要么

def foo():
    __import__('herp.derp')

6

亚伦·霍尔的答案对我有用。只想提一件事,

如果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()

4

我找到了在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次测试


3

如果这样做,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)的影响的深入理解什么是重要的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.