如何模拟导入

时间:2011-12-28 15:51:33

标签: python mocking python-import

模块A在其顶部包含import B。但是,在测试条件下,我希望在B(模拟A)中mock A.B并完全避免导入B

事实上,B并未故意安装在测试环境中。

A是被测单位。我必须导入A及其所有功能。 B是我需要模拟的模块。但是,如果B做的第一件事是导入A,我如何在A内模仿B并停止A导入真实B

(没有安装B的原因是我使用pypy进行快速测试,不幸的是B还与pypy兼容。)

怎么可以这样做?

8 个答案:

答案 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

  1. sys.modules['herp']是否存在?否则导入它。如果还不是ImportError
  2. sys.modules['herp.derp']是否存在?否则导入它。如果还不是ImportError
  3. 获取foo的属性sys.modules['herp.derp']。其他ImportError
  4. foo = sys.modules['herp.derp'].foo
  5. 这个被黑客入侵的解决方案有一些缺点:如果其他东西依赖于模块路径中的其他东西,那么这种方法就会将其搞砸。此适用于内联导入的内容,例如

    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 - 块中模拟您的模块/类。

指向相关文档的指针:

docs.python.org: __import__

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