关于python:如何对装饰方法进行单元测试

How to unit test a decorated method

假设我必须单元测试methodA,在以下类中定义:

1
2
3
4
5
6
7
8
9
10
11
class SomeClass(object):

    def wrapper(fun):
        def _fun(self, *args, **kwargs):
            self.b = 'Original'
            fun(self, *args, **kwargs)
        return _fun

    @wrapper
    def methodA(self):
        pass

我的测试类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from mock import patch

class TestSomeClass(object):

    def testMethodA(self):
        def mockDecorator(f):
            def _f(self, *args, **kwargs):
                self.b = 'Mocked'
                f(self, *args, **kwargs)
            return _f

        with patch('some_class.SomeClass.wrapper', mockDecorator):
            from some_class import SomeClass
            s = SomeClass()
            s.methodA()
            assert s.b == 'Mocked', 's.b is equal to %s' % s.b

如果我运行测试,我点击了断言:

1
2
3
File"/home/klinden/workinprogress/mockdecorators/test_some_class.py", line 17, in testMethodA
    assert s.b == 'Mocked', 's.b is equal to %s' % s.b
AssertionError: s.b is equal to Original

如果我在测试中粘贴一个断点,在修补之后,我可以看到wrapper已经被模拟出来了,但是methodA仍然引用了旧的包装器:

1
2
3
4
(Pdb) p s.wrapper
<bound method SomeClass.mockDecorator of <some_class.SomeClass object at 0x7f9ed1bf60d0>>
(Pdb) p s.methodA
<bound method SomeClass._fun of <some_class.SomeClass object at 0x7f9ed1bf60d0>>

知道问题出在这里吗?


仔细研究后,我找到了解决方案。

因为猴子补丁似乎没有效果(我也尝试了一些
其他解决方案),我挖掘了函数内部,这被证明是富有成效的。

Python 3
你很幸运 - 只需使用wraps装饰器,它创建一个__wrapped__属性,该属性又包含包装函数。 有关详细信息,请参阅上面的链接答案。

Python 2
即使您使用@wraps,也不会创建任何花哨的属性。
但是,您只需要意识到包装器方法只执行闭包:因此您将能够在其func_closure属性中找到包装函数。

在原始示例中,包装函数位于:s.methodA.im_func.func_closure[0].cell_contents

结束(哈!)
我沿着这条线创建了一个getWrappedFunction帮助器,以方便我的测试:

1
2
3
@staticmethod
def getWrappedFunction(wrapper):
    return wrapper.im_func.func_closure[0].cell_contents

YMMV,特别是如果你做了花哨的东西,并在封闭中包含其他对象。