使用Python Mock模拟函数
我正在尝试使用 Python 的 mock 模块 来模拟一个函数,这个函数会返回一些外部内容。
不过,我在模拟那些被导入到模块中的函数时遇到了一些麻烦。
举个例子,在 util.py
文件中,我有:
def get_content():
return "stuff"
我想模拟 util.get_content
,让它返回其他内容。
我尝试了这个:
util.get_content=Mock(return_value="mocked stuff")
但是如果 get_content
在另一个模块中被调用,它似乎从来没有返回我模拟的对象。我是不是在使用 Mock
的时候漏掉了什么?
需要注意的是,如果我直接调用以下内容,事情就能正常工作:
>>> util.get_content=Mock(return_value="mocked stuff")
>>> util.get_content()
"mocked stuff"
然而,如果 get_content
是从另一个模块内部调用的,它会调用原始的函数,而不是我模拟的版本:
>>> from mymodule import MyObj
>>> util.get_content=Mock(return_value="mocked stuff")
>>> m=MyObj()
>>> m.func()
"stuff"
这是 mymodule.py
的内容:
from util import get_content
class MyObj:
def func():
get_content()
所以我想问的是——我该如何从我调用的模块内部调用模拟的函数版本呢?
看起来是 from module import function
可能是问题所在,因为它没有指向模拟的函数。
5 个回答
你需要在使用这个函数的地方进行修补。在你的例子中,就是在mymodule模块里。
import mymodule
>>> mymodule.get_content = Mock(return_value="mocked stuff")
>>> m = mymodule.MyObj()
>>> m.func()
"mocked stuff"
这里有相关的文档参考: http://docs.python.org/dev/library/unittest.mock.html#where-to-patch
一般情况下,我们会使用 mock
里的 patch
。来看下面的例子:
utils.py
def get_content():
return 'stuff'
mymodule.py
from util import get_content
class MyClass(object):
def func(self):
return get_content()
test.py
import unittest
from mock import patch
from mymodule import MyClass
class Test(unittest.TestCase):
@patch('mymodule.get_content')
def test_func(self, get_content_mock):
get_content_mock.return_value = 'mocked stuff'
my_class = MyClass()
self.assertEqual(my_class.func(), 'mocked stuff')
self.assertEqual(get_content_mock.call_count, 1)
get_content_mock.assert_called_once()
注意这里是如何模拟 get_content
的,它不是 util.get_content
,而是 mymodule.get_content
,因为我们是在 mymodule
里使用它。
以上内容是在 mock 版本 2.0.0、nosetests 版本 1.3.7 和 Python 版本 2.7.9 下测试过的。
我想我找到了一种解决方法,不过现在还不太清楚怎么解决更一般的情况。
在 mymodule
中,如果我把
from util import get_content
class MyObj:
def func():
get_content()
换成
import util
class MyObj:
def func():
util.get_content()
那么 Mock
似乎就能被调用了。看起来命名空间需要匹配(这很合理)。但是奇怪的是,我本来以为
import mymodule
mymodule.get_content = mock.Mock(return_value="mocked stuff")
在我使用 from/import 语法的原始情况下会有效(这会把 get_content
引入到 mymodule
中)。但这仍然指向未被模拟的 get_content
。
结果发现命名空间很重要——在写代码的时候一定要记住这一点。