如何嘲笑导入

模块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

  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

这种黑客入侵的解决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