如何模拟导入

Jon*_*han 126 python mocking python-import

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

实际上,B并非故意安装在测试环境中.

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

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

怎么可以这样做?

Rob*_*ers 119

您可以sys.modules['B']在导入之前分配到A以获得所需内容:

test.py:

import sys
sys.modules['B'] = __import__('mock_B')
import A

print(A.B.__name__)
Run Code Online (Sandbox Code Playgroud)

A.py:

import B
Run Code Online (Sandbox Code Playgroud)

注意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
Run Code Online (Sandbox Code Playgroud)

  • 对于那些将来看到这一点的人,请注意,在测试中改变 sys.modules 可能会对套件中的其他测试产生意想不到的后果。我强烈建议使用 with 上下文,如下所示: `with patch.dict(sys.modules, {"B": Mock()}): import A ` (7认同)
  • @reclosedev - 那里有[Magic Mock](http://www.voidspace.org.uk/python/mock/magicmock.html#magic-mock) (6认同)
  • 如何在测试结束时重置此模拟导入,以便其他单元测试文件不会受到模拟对象的影响? (4认同)
  • 请记住,"模拟"不会修补一些魔术属性(`__%s__`),如`__name__`. (3认同)
  • 如何撤消此操作以使 B 导入再次出现 ImportError?我尝试了 `sys.modules['B'] = None` 但它似乎不起作用。 (3认同)
  • 要撤消模拟,请使用:``sys.modules.pop("B")``` (3认同)
  • 为了清楚起见,您应该编辑答案以实际导入“mock”,然后调用“mock.Mock()” (2认同)

sie*_*z0r 23

内置__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
Run Code Online (Sandbox Code Playgroud)

A看起来像:

import B

def a():
    return B.func()
Run Code Online (Sandbox Code Playgroud)

A.a()b_mock.func()也可以嘲笑的回报.

b_mock.func.return_value = 'spam'
A.a()  # returns 'spam'
Run Code Online (Sandbox Code Playgroud)

  • 使用Python 3.4.3,我得到`ImportError:没有名为'__builtin __'的模块 (3认同)
  • @LucasCimon,将 python3 的 `__builtin__` 替换为 `builtins` (https://docs.python.org/3/whatsnew/3.0.html?highlight=__builtin__) (2认同)

Aar*_*all 14

如何模拟导入,(模拟AB)?

模块A在其顶部包括导入B.

很简单,只需在导入之前模拟sys.modules中的库:

if wrong_platform():
    sys.modules['B'] = mock.MagicMock()
Run Code Online (Sandbox Code Playgroud)

然后,只要A不依赖于从B对象返回的特定类型的数据:

import A
Run Code Online (Sandbox Code Playgroud)

应该工作.

你也可以模仿import A.B:

即使您有子模块,这也有效,但您需要模拟每个模块.说你有这个:

from foo import This, That, andTheOtherThing
from foo.bar import Yada, YadaYada
from foo.baz import Blah, getBlah, boink
Run Code Online (Sandbox Code Playgroud)

要进行模拟,只需在导入包含上述模块的模块之前执行以下操作:

sys.modules['foo'] = MagicMock()
sys.modules['foo.bar'] = MagicMock()
sys.modules['foo.baz'] = MagicMock()
Run Code Online (Sandbox Code Playgroud)

(我的经验:我有一个依赖,可以在一个平台上工作,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)
Run Code Online (Sandbox Code Playgroud)

然后代码需要一些时间来运行,就像真正的方法一样.


小智 10

亚伦霍尔的回答对我有用。只想提一件重要的事,

如果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()
Run Code Online (Sandbox Code Playgroud)


Ant*_*ile 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
Run Code Online (Sandbox Code Playgroud)

这是如此荒谬复杂的原因是当导入发生时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
Run Code Online (Sandbox Code Playgroud)

要么

def foo():
    __import__('herp.derp')
Run Code Online (Sandbox Code Playgroud)


Hun*_*_71 5

我找到了在 Python 中模拟导入的好方法。这是Eric 的 Zaadi解决方案在这里找到,我只是在我的Django应用程序中使用它。

我有一个类SeatInterface,它是Seat模型类的接口。所以在我的seat_interface模块中,我有这样一个导入:

from ..models import Seat

class SeatInterface(object):
    (...)
Run Code Online (Sandbox Code Playgroud)

我想SeatInterface用模拟Seat类为类创建隔离测试FakeSeat。问题是 - 如何离线运行测试,其中 Django 应用程序已关闭。我有以下错误:

ImproperlyConfigured:请求设置 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
Run Code Online (Sandbox Code Playgroud)

然后 test 神奇地运行 OK :)

.
在 0.002 秒内运行 1 次测试

好的