如何模拟导入

模块 A的顶部包括 import B。然而,在测试条件下,我希望 嘲笑 BA(模拟 A.B) ,并完全避免导入 B

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

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

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

这怎么可能呢?

93449 次浏览

你可以在导入 A之前分配到 sys.modules['B']来得到你想要的:

返回文章页面

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


print(A.B.__name__)

返回文章页面

import B

注意 B.py 不存在,但是当运行 test.py时不返回错误,并且 print(A.B.__name__)打印 mock_B。您仍然需要创建一个模拟 B的实际函数/变量等的 mock_B.py。或者你可以直接指定一个 Mock():

返回文章页面

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

如果你执行 import ModuleB,你实际上是在调用内建方法 __import__:

ModuleB = __import__('ModuleB', globals(), locals(), [], -1)

您可以通过导入 __builtin__模块并在 __builtin__.__import__方法周围创建一个包装器来覆盖这个方法。或者你可以玩从 imp模块的 NullImporter钩。捕获异常并在 except块中模仿模块/类。

指向相关文件的指针:

Http://docs.python.org/library/Functions.html # _ _ import _ _”rel = “ nofollow”> docs.python.org: __import__ < a href = “ http://docs.python.org/library/Functions.html # _ _ import _ _”rel = “ nofollow”> docs.python.org: __import__

使用 imp 模块访问 Import 内部结构

希望这个能帮上忙。非常喜欢建议你进入更神秘的 Python 编程领域,a)坚定地理解你真正想要达到的目标,b)彻底地理解其中的含义是很重要的。

我知道我来得有点晚了,但是这里有一个有点疯狂的方法可以用 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. 获取 sys.modules['herp.derp']的属性 foo,否则获取 ImportError
  4. foo = sys.modules['herp.derp'].foo

这种拼凑的解决方案有一些缺点: 如果其他东西依赖于模块路径中的其他东西,那么这种方法会把它搞砸。此外,这个 只有适用于内联导入的内容,如

def foo():
import herp.derp

或者

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

内置的 __import__可以通过“模拟”库进行模拟,以获得更多控制:

# 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'

< strong > Python 3注意: 如 3.0的变更日志中所述,__builtin__现在被命名为 builtins:

将模块 __builtin__重命名为 builtins(删除下划线,添加一个“ s”)。

如果将 Python3中的 __builtin__替换为 builtins,则此答案中的代码可以正常工作。

如何模拟导入,(模拟 A.B) ?

模块 A 的顶部包括导入 B。

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

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)

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

我找到了在 Python 中模拟导入的好方法。它的 Eric 是 Zaadi解决方案发现 给你,我只是在我的 姜戈应用程序中使用。

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

from ..models import Seat


class SeatInterface(object):
(...)

我想用模拟的 Seat类作为 FakeSeatSeatInterface类创建独立的测试。问题是——如何离线运行测试,Django 应用程序在哪里停止运行。我犯了以下错误:

不正确配置: 请求设置 BASE _ DIR,但设置不是 你必须定义环境变量 DJANGO _ SETTING _ MODULE 或调用 setings.configure ()访问之前 设置

在0.078秒内进行了一次测试

失败(错误 = 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:)

< p > . < br > 在0.002 s

进行1次测试

好的

艾伦 · 霍尔的回答对我有用。 我只想说一件重要的事

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

我知道这是一个相当老的问题,但我发现自己最近几次回到这个问题,并希望分享一个简明的解决方案。

import sys
from unittest import mock




def mock_module_import(module):
"""Source: https://stackoverflow.com/a/63584866/3972558"""
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():
# use something that relies upon "some_module" here
assert True

今天我发现自己面临着一个类似的问题,我决定用不同的方式来解决它。您可以简单地将模拟模块添加到 sys.path中,而不是在 Python 的导入机制之上进行修改,从而让 Python 更喜欢它而不是原始模块。

  1. 在子目录中创建替换模块,例如:

    mkdir -p test/mocked-lib
    ${EDITOR} test/mocked-lib/B.py
    
  2. 在导入 A之前,将这个目录插入到 sys.path。我使用的是 派特,所以在我的 test/conftest.py中,我只需要:

    import os.path
    import sys
    
    
    
    
    sys.path.insert(0, os.path.join(os.path.dirname(__file__), "mocked-lib"))
    

现在,当测试套件运行时,mocked-lib子目录被添加到 sys.path中,而 import A使用来自 mocked-libB