Python:在模拟补丁类装饰器上调用stop

时间:2014-08-01 22:44:48

标签: python unit-testing mocking

Mock documentation描述了一种简单而优雅的方法,可以将补丁应用于TestCase内的所有测试方法:

@patch('foo.bar')
@patch('foo.baz')
@patch('foo.quux')
@patch('foo.narf')
class FooTest(TestCase):

    def test_foo(self, bar, baz, quux, narf):
        """ foo """
        self.assertTrue(False) 

然而,我遇到过这个方法的一个问题是,如果我想在其中一个测试方法中的一个补丁上调用stop(),那么就不会出现这种情况。无论如何都要获取对patcher对象的引用 - 传递给方法的唯一方法是模拟对象,在本例中为barbazquux,{{1 }}。

我发现解决此问题的唯一方法是转到模拟文档中描述的模式,其中实例化的修补程序在narf的{​​{1}}方法中启动并且停在setUp方法内。这符合我的目的,但增加了许多额外的样板,并没有像类装饰器方法那样优雅。

还有其他方法可以解决这个问题吗?

1 个答案:

答案 0 :(得分:7)

<强> 1

假设您要暂时恢复方法中的foo.narffoo.narf在装饰函数的上下文中是MagicMock对象。该对象具有_mock_wraps属性,在调用mock时将调用该属性!因此,在您的模块顶部_narf = foo.narf和测试用例foo.narf._mock_wraps = _narf

问题是这只会传递给真正的函数,而不是实际交换它,这意味着一些测试用例将失败(例如,如果它们依赖于函数对象实际上是“本身”)。如果你的模拟有其他属性,可以干扰(我没有测试太多),因为对_mock_wraps()的直通调用位于首先考虑其他属性的方法的底部。模拟。

<强> 2

patch()装饰器涉及每个patcher(每个方法单独的副本)被添加到名为patchings的列表中,该列表是方法本身的字段。即您可以self.test_foo.patchings访问此列表,然后查找所需的列表。

但是,当您使用start()作为装饰器时,实际上并未调用stop()patch(),并且一旦您开始进入并更改它,行为就会变得棘手。所以我写了这个上下文管理器。

class unpatch:
    def __init__(self, name, method):
        compare = patch(name)
        self.patcher = next((
            p for p in method.patchings
            if p.target == compare.getter()
            and p.attribute == compare.attribute
        ), None)
        if self.patcher is None:
            raise ValueError(name)

    def __enter__(self):
        self.patcher.__exit__()

    def __exit__(self, *exc_info):
        self.patcher.__enter__()

在测试用例中,您可以像这样使用它:

with unpatch('foo.narf', self.test_foo):
    foo.narf()

免责声明:这是黑客攻击。