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

Lia*_*avK 6 python unit-testing mocking

Mock 文档描述了一种简单而优雅的方法,可以将补丁应用于以下内容中的所有测试方法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) 
Run Code Online (Sandbox Code Playgroud)

但是,我在这个方法中遇到的一个问题是,如果我想在其中一个测试方法中的一个补丁上调用stop(),那么似乎无法获得对补丁的引用object - 传递给方法的唯一东西是mock对象,在本例barbazquux,narf.

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

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

Jas*_*n S 7

1

假设您要foo.narf在方法中临时恢复.foo.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__()
Run Code Online (Sandbox Code Playgroud)

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

with unpatch('foo.narf', self.test_foo):
    foo.narf()
Run Code Online (Sandbox Code Playgroud)

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