使用Python Mock模拟函数

110 投票
5 回答
138904 浏览
提问于 2025-04-16 13:35

我正在尝试使用 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 个回答

29

你需要在使用这个函数的地方进行修补。在你的例子中,就是在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

68

一般情况下,我们会使用 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 下测试过的。

47

我想我找到了一种解决方法,不过现在还不太清楚怎么解决更一般的情况。

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

结果发现命名空间很重要——在写代码的时候一定要记住这一点。

撰写回答