我开始使用python mock library进行测试。我想模拟一个在被测模块的命名空间中导入的模块,而不是实际导入它或要求它首先存在(即抛出一个ImportError)。
假设存在以下代码:
foo.py
import helpers
def foo_func():
return helpers.helper_func()
当'helpers.py'在任何地方都不存在时,目标是测试foo_func(),如果确实存在,则表现得好像不行。
首先尝试使用超酷的@patch装饰器:
from mock import patch, sentinel
import foo
@patch("foo.helpers")
def foo_test(mock):
mock.helper_func.return_value = sentinel.foobar
assert foo.foo_func() == sentinel.foobar
如果可以导入“帮助程序”模块,则此方法有效。如果它不存在,我会得到一个ImportError。
接下来尝试使用patch,sans decorator:
from mock import patch, sentinel, Mock
import foo
helpers_mock = patch("foo.helpers")
helpers_mock.start()
def foo_test():
helpers_mock.helper_func = Mock('helper_func')
helpers_mock.helper_func.return_value = sentinel.foobar
assert foo.foo_func() == sentinel.foobar
同样,如果“helpers”丢失,这不起作用......如果存在,则断言失败。不确定为什么会这样。
第三次尝试,当前的解决方案:
import sys
helpers_mock = Mock(name="helpers_mock", spec=['helper_func'])
helpers_mock.__name__ = 'helpers'
sys.modules['helpers'] = helpers_mock
import foo
def foo_test():
helpers_mock.helper_func.return_value = sentinel.foobar
assert foo.foo_func() == sentinel.foobar
无论是否存在“helpers.py”,此测试都会通过。
这是实现这一目标的最佳方式吗?我使用的模拟库是否提供了替代方案?我还有什么方法可以做到这一点?
答案 0 :(得分:4)
你有点想念Mock是什么意思。当你想要一个具有特定接口的对象时,你应该构建它们,而不管它是如何实现的。
你正在做的是尝试重新实现python的模块系统,加上它是一个非常可怕的滥用全局变量来启动。
不要将foo作为模块,而是创建一个Foo类,并在构造函数中传入帮助器。
class Foo(object):
def __init__(self, helpers):
self.helpers = helpers
# then, instead of import foo:
foo = Foo(mock_helpers)
即使真正的“帮助者”实际上是一个模块,也没有理由需要在测试中通过import
搞砸sys.modules并进行设置。
如果foo必须是一个模块,那也没关系,但你这样做:
# foo.py
class Foo(object):
pass # same code as before, plus foo_func
try:
import whatever
_singleton = Foo(whatever)
except ImportError:
_singleton = Foo(something_else)
def foo_func():
return _singleton.foo_func()
标准库的大块以这种方式工作。它几乎是定义类似单件模块的标准。
答案 1 :(得分:1)
我有一个类似的问题,因为helpers
库需要特殊的硬件,所以无法加载。
与其对要测试的代码进行根本性更改,还可以按照how to add a package to sys path for testing
sys.path
中插入一个“伪”目录。
import os, sys
fake_dir = os.path.join(os.path.dirname(__file__), 'fake')
assert(os.path.exists(fake_dir))
sys.path.insert(0, fake_dir)
import foo
from unittest.mock import sentinel
def foo_test():
foo.helpers.helper_func.return_value = sentinel.foobar
assert foo.foo_func() == sentinel.foobar
其中fake
的结构为:
.
├── fake/
│ └── helpers/
│ └── __init__.py
├── foo.py
└── helpers/
和__init__.py
有
from unittest.mock import Mock
helper_func = Mock()