如何嘲笑导入
模块A
在其顶部包含import B
然而,在testing条件下,我想嘲笑 B
(模拟AB
),完全避免导入B
实际上, B
不是故意安装在testing环境中的。
A是被testing的单元。 我必须导入A的所有function。 B是我需要模拟的模块。 但是,如果A的第一个东西是导入B,我怎么能在A中模拟B,并停止A导入真实的B?
(B没有安装的原因是我用pypy进行快速testing,不幸的是B还不兼容pypy。)
这怎么可能呢?
您可以在导入A
之前将其分配给sys.modules['B']
以获取所需内容:
test.py :
import sys sys.modules['B'] = __import__('mock_B') import A print(AB__name__)
答 :
import B
注意B.py不存在,但是当运行test.py
不返回错误, print(AB__name__)
打印mock_B
。 你仍然需要创build一个mock_B.py
模拟B的实际函数/variables/等等。 或者你可以直接指定一个模拟():
test.py :
import sys sys.modules['B'] = Mock() import A
内build的__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()
Aa()
返回可以被b_mock.func()
。
b_mock.func.return_value = 'spam' Aa() # returns 'spam'
我意识到我在这里的晚会有点晚,但是这里有一个有点疯狂的方式来使用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
)
-
sys.modules['herp']
是否存在? 否则导入它。 如果仍然不ImportError
-
sys.modules['herp.derp']
是否存在? 否则导入它。 如果仍然不ImportError
- 获取
sys.modules['herp.derp']
属性foo
。 否则,ImportError
-
foo = sys.modules['herp.derp'].foo
这种黑客入侵的解决scheme存在一些缺点:如果其他东西依赖于模块path中的其他东西,则将其拧紧。 此外, 这只适用于内部导入的东西,如
def foo(): import herp.derp
要么
def foo(): __import__('herp.derp')
如何嘲笑import,(模拟AB)?
模块A在其顶部包含导入B.
很简单,只需在sys.modules中导入之前嘲笑库:
if wrong_platform(): sys.modules['B'] = mock.MagicMock()
然后,只要A
不依赖从B的对象返回的特定types的数据:
import A
应该只是工作。
你也可以模拟import AB
:
即使你有子模块,这也可以工作,但你会想模拟每个模块。 说你有这个:
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,但是在我们运行日常testing的Linux上没有工作,所以我需要模拟testing的依赖关系,幸运的是它是一个黑盒子,所以我不需要build立很多互动。)
嘲笑的副作用
附录:其实我需要模拟一些需要花费时间的副作用。 所以我需要一个对象的方法睡一会儿。 这将是这样的工作:
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)
然后代码需要一些时间来运行,就像真正的方法一样。
如果你做一个import ModuleB
你真的调用内置方法__import__
为:
ModuleB = __import__('ModuleB', globals(), locals(), [], -1)
你可以通过导入__builtin__
模块来覆盖这个方法,并围绕__builtin__.__import__
方法进行封装。 或者你可以使用imp
模块的NullImporter
挂钩。 捕捉exception,并在except
block中模拟你的模块/类。
指向相关文档的指针:
__import__
使用imp模块访问Import内部
我希望这有帮助。 强烈build议您进入python编程的更加神秘的界限,并且a)牢固地理解您真正想要实现的目标,b)彻底理解这些影响是非常重要的。
你可以使用补丁和MagicMock
#Python3.4 from unittest.mock import patch, MagicMock #older versions from mock import patch, MagicMock import A mock = MagicMock() class ATestCase(TestCase): def setUp(): #function mock.foo.return_value = 'value' #variable mock.bar=1234 @patch('B',mock) test_a_should_display_info(self): #code and assert print(AB) #<MagicMock id='140047253898912'>
我发现很好的方式来嘲笑Python中的导入。 这是在我的Django应用程序中使用的Eric的Zaadi解决scheme。
我已经有Seat
类接口,它是Seat
模型类的接口。 所以在我的seat_interface
模块中,我有这样一个导入:
from ..models import Seat class SeatInterface(object): (...)
我想为SeatInterface
类创build一个独立的testing, SeatInterface
Seat
类作为FakeSeat
。 问题是 – 如何脱机运行testing,Django应用程序closures。 我有以下错误:
不正确configuration:请求设置BASE_DIR,但设置未configuration。 在访问设置之前,您必须定义环境variablesDJANGO_SETTINGS_MODULE或调用settings.configure()。
冉1testing在0.078s
失败(错误= 1)
解决scheme是:
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
然后神奇testing运行OK 🙂
。
冉1testing在0.002s好