python mock - 在不妨碍实现的情况下修补方法

wim*_*wim 47 python mocking

是否有一种干净的方法来修补对象,以便您assert_call*在测试用例中获得帮助程序,而无需实际删除操作?

例如,如何修改该@patch行以获得以下测试传递:

from unittest import TestCase
from mock import patch


class Potato(object):
    def foo(self, n):
        return self.bar(n)

    def bar(self, n):
        return n + 2


class PotatoTest(TestCase):

    @patch.object(Potato, 'foo')
    def test_something(self, mock):
        spud = Potato()
        forty_two = spud.foo(n=40)
        mock.assert_called_once_with(n=40)
        self.assertEqual(forty_two, 42)
Run Code Online (Sandbox Code Playgroud)

我可能会一起使用side_effect,但是我希望有一种更好的方法可以在所有函数,类方法,静态方法,未绑定方法等上以相同的方式工作.

fal*_*tru 33

与您的类似解决方案,但使用wraps:

def test_something(self):
    spud = Potato()
    with patch.object(Potato, 'foo', wraps=spud.foo) as mock:
        forty_two = spud.foo(n=40)
        mock.assert_called_once_with(n=40)
    self.assertEqual(forty_two, 42)
Run Code Online (Sandbox Code Playgroud)

根据文件:

wrap:要包装的模拟对象的项.如果换行不是None,则调用Mock会将调用传递给包装对象(返回实际结果).模拟器上的属性访问将返回一个Mock对象,该对象包装被包装对象的相应属性(因此,尝试访问不存在的属性将引发AttributeError).


class Potato(object):

    def spam(self, n):
        return self.foo(n=n)

    def foo(self, n):
        return self.bar(n)

    def bar(self, n):
        return n + 2


class PotatoTest(TestCase):

    def test_something(self):
        spud = Potato()
        with patch.object(Potato, 'foo', wraps=spud.foo) as mock:
            forty_two = spud.spam(n=40)
            mock.assert_called_once_with(n=40)
        self.assertEqual(forty_two, 42)
Run Code Online (Sandbox Code Playgroud)

  • 不,因为通过在装饰器中创建一个新的Potato实例,你失去了实际被测试的对象的状态,你需要绑定方法.. (2认同)
  • 我想知道补丁是否应该是`patch.object(spud,'foo',wraps = spud.foo)`,以便代码对特定实例进行补丁。尽管在这种情况下并没有什么实际区别,但是当前代码在类级别(所有实例)进行修补,但是将绑定到特定实例的函数包装在一起。我认为这可能会烧死别人。 (2认同)
  • @falsetru,我确实看到了这一点,但确实认为另一个 SO 读者使用时的类/实例差异可能会烧伤使用此示例的人。例如,如果测试代码使用不同的实例值测试 spud 和 spud2。调用 spud2.foo 实际上会返回 spud.foo 的结果。这就是为什么我认为修补的对象应该是 spud 而不是它的类。 (2认同)
  • 这也适用于 Python 2.7 的 PyPI `mock` 库(从文档中它不是很明显,因为 `wraps` 不是 `patch.object` 的文档化 kwarg,而是作为 `**kwargs` 传递到 `MagicMock ` _is_ 记录的地方) (2认同)

Int*_*rer 11

对于那些不介意使用 的人side_effect,这里有一个具有一些优点的解决方案:

  • 使用装饰器语法
  • 修补未绑定的方法,我发现它更通用
    • 需要在断言中包含实例
class PotatoTest(TestCase):

    @patch.object(Potato, 'foo', side_effect=Potato.foo, autospec=True)
    def test_something(self, mock):
        spud = Potato()
        forty_two = spud.foo(n=40)
        mock.assert_called_once_with(spud, n=40)
        self.assertEqual(forty_two, 42)
Run Code Online (Sandbox Code Playgroud)

  • AFAICT 如果您无法在测试中控制 Potato 的实例化,这是唯一的方法(如果可以,那么接受答案中的“wraps”方法将起作用)...这个方法的优点是您不需要“应用模拟时的 spud` (2认同)

wim*_*wim 8

这个答案解决了用户Quuxplusone的赏金中提到的额外要求:

对我的用例来说重要的是它可以使用@patch.mock,即它不需要我在构造Potato(spud在这个例子中)的实例和我的调用之间插入任何代码spud.foo.我需要从一开始就使用模拟方法spud创建foo,因为我无法控制spud创建的位置.

使用装饰器可以毫不费力地实现上述用例:

import unittest
import unittest.mock  # Python 3

def spy_decorator(method_to_decorate):
    mock = unittest.mock.MagicMock()
    def wrapper(self, *args, **kwargs):
        mock(*args, **kwargs)
        return method_to_decorate(self, *args, **kwargs)
    wrapper.mock = mock
    return wrapper

def spam(n=42):
    spud = Potato()
    return spud.foo(n=n)

class Potato(object):

    def foo(self, n):
        return self.bar(n)

    def bar(self, n):
        return n + 2

class PotatoTest(unittest.TestCase):

    def test_something(self):
        foo = spy_decorator(Potato.foo)
        with unittest.mock.patch.object(Potato, 'foo', foo):
            forty_two = spam(n=40)
        foo.mock.assert_called_once_with(n=40)
        self.assertEqual(forty_two, 42)


if __name__ == '__main__':
    unittest.main()
Run Code Online (Sandbox Code Playgroud)

如果替换的方法接受在测试下修改的可变参数,则可能希望初始化a CopyingMock代替MagicMockspy_decorator内部.