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


73

有没有一种干净的方法来修补对象,以使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)

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


同样,断言foo被调用没有太多意义,因为测试本身正在调用它,而不是正在测试的其他代码。同样,forty_twotest设置为特定值的测试(而非经过测试的代码)似乎没有什么价值。
chepner 2014年

2
这是一个过程。实际代码是修补在其他模块中创建的实例,深度嵌套的实例
2014年

2
我也有同样的问题; 对我来说,重要的是,解决方案应该要求我在我的土豆实例的构造(之间插入任何代码spud在这个例子中)和我的呼唤spud.foo。我需要从一开始就使用模拟方法spud来创建foo,因为我无法控制创建spud和调用其foo方法的代码路径。
Quuxplusone

1
回到几年后,我发现spy在pytest套件中很有用。
wim

Answers:


59

与您类似的解决方案,但使用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)

根据文档

wraps:要包装的模拟对象的项目。如果wraps不为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)

谢谢,这比我的要好一些。.您知道在装饰器的使用中而不是在上下文管理器中使用的任何方法吗?
2014年

2
不,因为通过在装饰器中创建新的Potato实例,您会丢失实际正在测试的对象的状态,因此需要绑定方法..
wim 2014年

5
我想知道是否应该patch.object(spud, 'foo', wraps=spud.foo)改用补丁,以便代码对特定实例进行补丁。尽管在这种情况下并没有什么实际区别,但是当前代码在类级别(所有实例)进行修补,但是将绑定到特定实例的函数包装在一起。我认为这可能会烧死别人。
studgeek '17

1
@falsetru,我确实看到了,但确实认为,使用另一个示例的类/实例差异在被另一个SO读者使用时可能会烧死某人。例如,如果测试代码的spud和spud2在测试中具有不同的实例值。调用spud2.foo实际上会返回spud.foo的结果。这就是为什么我认为修补的对象应该是桩而不是其类。
studgeek '17

1
这也工作正常使用的PyPImock库为Python 2.7(它不是从文档显而易见的wraps是不是一个文件kwarg patch.object,而是作为传递**kwargsMagicMock记录在案)
Anentropic

13

此答案解决了Quuxplusone用户提供的赏金中提到的其他要求:

对于我的用例而言,重要的是它可以与之一起使用@patch.mock,即,它不需要我在构建实例Potatospud在此示例中)和调用之间插入任何代码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()

如果替换的方法接受在测试中修改过的可变参数,则您可能希望初始化*来代替spy_decorator内部。 CopyingMockMagicMock

*这是我在PyPI上作为copyingmock lib发布的文档中的食谱


我还没有完全理解wim在这里做什么,但是“谢谢”-我整个下午都在这座砖墙上打我的头!
Paul D Smith
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.