【问题标题】:Mocking Functions Using Python Mock使用 Python Mock 模拟函数
【发布时间】:2011-07-14 06:29:57
【问题描述】:

我正在尝试使用 python mock module 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 可能是这里的罪魁祸首,因为它没有指向 Mocked 函数。

【问题讨论】:

  • 我遵循了您的确切描述(在 python 2.5 中,在 linux 上使用 mock 0.7.0),它运行良好。你有更多的细节可以提供吗?
  • Hmm - 从顶级范围调用该函数时,它看起来像预期的那样运行。但是,当它从另一个模块或函数内部(即调用堆栈的更下方)被调用时,它不会表现出 Mock-ed 行为。我正在澄清这个例子来说明这一点。
  • 好的,我尝试了您的新描述 - 我仍然得到正确答案,即使来自 mymodule.func()。对我来说唯一的区别是我的mymodule.func()returnsutil.get_content(),而不只是调用它。我觉得你的描述中一定还缺少一些信息。您是否真的尝试过上面的确切描述?你的实际代码是什么?
  • 对此感到抱歉 - 我避免粘贴大段代码。你是对的 - 我的简化示例并没有完全失败,但我认为我已经部分找到了解决方案。会更新这个。
  • 奇怪 - 在我更复杂的 Django 测试用例中,事情并没有正常工作,但是当我尝试将它提炼下来时,Mock 对象似乎正在按预期传递。我猜在创建命名空间略有不同的导入中存在一些差异。

标签: python unit-testing mocking


【解决方案1】:

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

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

原来命名空间很重要——只需要在编写代码时牢记这一点。

【讨论】:

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

一般情况是使用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 v2.0.0、nosetests v1.3.7 和 python v2.7.9 进行了测试。

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 如果我们想模拟像open这样的内置函数怎么办?
    【解决方案4】:

    假设您正在模块 foobar 内创建模拟:

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

    如果你在没有先导入foobar的情况下导入mymodule并调用util.get_content,你的mock将不会被安装:

    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"
    

    请注意,只要在调用 util.get_content 之前评估 foobar,就可以从任何地方导入 foobar(模块 A 导入 B 导入 foobar)。

    【讨论】:

      【解决方案5】:

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

      因此,您可以使用以下方式将模块实用程序转换为类:

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

      然后正确地模拟补丁。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-12-21
        • 2016-11-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多