题 使用python mock模拟函数


我正在尝试使用python模块模块(http://www.voidspace.org.uk/python/mock/index.html)模拟一个函数(返回一些外部内容)。

我在模拟导入模块的函数时遇到了一些麻烦。

例如,在util.py中我有

def get_content():
  return "stuff"

我想模拟util.get_content,以便返回其他内容。

我正在尝试这个:

util.get_content=Mock(return_value="mocked stuff")

如果 get_content 在另一个模块中调用,它实际上似乎永远不会返回模拟对象。我是否错过了如何使用模拟的东西?

请注意,如果我调用以下内容,则可以正常工作:

>>> 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()

所以我想我的问题是 - 如何在我调用的模块中调用函数的Mocked版本?

看来, from module import function 这可能是责备,因为它没有指向Mocked函数。


47
2018-03-12 23:43


起源


我按照你的确切描述(在python 2.5中,使用模拟0.7.0,在linux上),它工作正常。您还有更多可以提供的详细信息吗? - Nate
嗯 - 当从顶级范围调用函数时,它看起来像预期的那样。但是,当从另一个模块或函数内部调用它时(即在调用堆栈的下方),它不会表现出Mock-ed行为。我正在澄清这个例子来说明这一点。 - shreddd
好的,我尝试了你的新描述 - 我是 仍然 得到正确答案,即使是 mymodule.func()。我唯一的区别就是我的 mymodule.func()  return小号 util.get_content(),而不只是称之为。我觉得你的描述中仍然缺少一些信息。你真的尝试过上面的确切描述吗?你的...是 实际 码? - Nate
很抱歉 - 我无法粘贴大部分代码。你是对的 - 我的还原性例子并没有完全失败,但我认为我已经部分找到了解决方案。会更新这个。 - shreddd
奇怪 - 在我更复杂的Django测试用例中,事情不能正常工作,但是当我尝试将其提取下来时,Mock对象似乎正按预期传递。我猜测在导入方面存在一些差异,这会产生稍微不同的命名空间。 - shreddd


答案:


我想我有一个解决方法,虽然我还不太清楚如何解决一般情况

在mymodule中,如果我更换

from util import get_content

class MyObj:    
    def func():
        get_content()

import util

class MyObj:    
    def func():
        util.get_content()

模拟似乎被调用。看起来命名空间需要匹配(这是有意义的)。然而,奇怪的是我会期待

import mymodule
mymodule.get_content = mock.Mock(return_value="mocked stuff")

在我使用from / import语法(现在将get_content拉入mymodule)的原始情况下执行操作。但这仍然指的是未被嘲笑的get_content。

结果是名称空间很重要 - 只需要在编写代码时牢记这一点。


24
2018-03-14 05:51



我真的很喜欢一般情况的答案。有时 from util import get_context 语法适用于模拟,有时则不适用。有人有解释吗? - johnboiles
非常感谢您的回答。我很困惑,为什么我的模拟工作在一些文件而不是在其他文件中。 - guettli
@johnboiles回答“为什么它有时有效,有时不行”:进口的顺序在这里很重要。如果你在导入之前执行了mock,那么如果首先执行导入,那么它就不起作用了。 - guettli


您必须修补正在使用它的功能。在你的情况下,将在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


19
2018-05-22 14:07





我们假设您正在创建模拟内部模块 foobar

import util, mock
util.get_content = mock.Mock(return_value="mocked stuff")

如果你导入 mymodule 并打电话 util.get_content 没有先导入 foobar,你的模拟将不会被安装:

import util
def func()
    print util.get_content()
func()
"stuff"

代替:

import util
import foobar   # substitutes the mock
def func():
    print util.get_content()
func()
"mocked stuff"

注意 foobar 可以从任何地方导入(模块A导入B导入foobar),只要 foobar 以前评估过 util.get_content 叫做。


8
2018-03-13 03:49





一般情况是使用 patch 从 mock。考虑以下:

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 v2.0.0测试,nosetests v1.3.7和python v2.7.9。


4
2018-06-15 17:55





虽然它不直接为您的问题提供答案,但另一种可能的替代方法是使用@staticmethod将您的函数转换为静态方法。

因此,您可以使用以下内容将模块工具转换为类:

class util(object):
     @staticmethod
     def get_content():
         return "stuff"

然后正确地模拟补丁。


1
2018-06-26 13:22