继承一个修补过的类

sih*_*hrc 12 python python-2.7 python-mock python-unittest

我有一个扩展unittest.TestCase的基类,我想修补那个基类,这样扩展这个基类的类也会应用补丁.

代码示例:

@patch("some.core.function", mocked_method)
class BaseTest(unittest.TestCase):
      #methods
      pass

class TestFunctions(BaseTest):
      #methods
      pass
Run Code Online (Sandbox Code Playgroud)

修补TestFunctions该类直接工作,但修补BaseTest类不会改变some.core.functionin 的功能TestFunctions.

mgi*_*son 7

一般来说,我更喜欢做这种事情setUp.您可以通过使用该tearDown方法确保在测试完成后清理补丁(或者,注册补丁的stop方法addCleanup):

class BaseTest(unittest.TestCase):
      def setUp(self):
            super(BaseTest, self).setUp()
            my_patch = patch("some.core.function", mocked_method)
            my_patch.start()
            self.addCleanup(my_patch.stop)

class TestFunctions(BaseTest):
      #methods
      pass
Run Code Online (Sandbox Code Playgroud)

如果你足够自律以便总是调用super你被覆盖的setUp方法,它应该可以正常工作.


Mar*_*nen 7

您可能需要一个元类:元类只是定义了如何创建类.默认情况下,所有类都是使用Python的内置类创建的type:

>>> class Foo:
...     pass
...
>>> type(Foo)
<class 'type'>
>>> isinstance(Foo, type)
True
Run Code Online (Sandbox Code Playgroud)

所以类实际上是实例type.现在,我们可以type创建一个自定义元类(一个创建类的类)的子类:

class PatchMeta(type):
    """A metaclass to patch all inherited classes."""
Run Code Online (Sandbox Code Playgroud)

我们需要控制类的创建,所以我们想要覆盖type.__new__这里,并patch在所有新实例上使用装饰器:

class PatchMeta(type):
    """A metaclass to patch all inherited classes."""

    def __new__(meta, name, bases, attrs):
        cls = type.__new__(meta, name, bases, attrs)
        cls = patch("some.core.function", mocked_method)(cls)
        return cls
Run Code Online (Sandbox Code Playgroud)

现在您只需使用__metaclass__ = PatchMeta以下方法设置元类:

class BaseTest(unittest.TestCase):
    __metaclass__ = PatchMeta
    # methods
Run Code Online (Sandbox Code Playgroud)

问题是这一行:

cls = patch("some.core.function", mocked_method)(cls)
Run Code Online (Sandbox Code Playgroud)

所以目前我们始终与论据装饰"some.core.function"mocked_method.相反,你可以使它使用类的属性,如下所示:

cls = patch(*cls.patch_args)(cls)
Run Code Online (Sandbox Code Playgroud)

然后添加patch_args到您的课程:

class BaseTest(unittest.TestCase):
    __metaclass__ = PatchMeta
    patch_args = ("some.core.function", mocked_method)
Run Code Online (Sandbox Code Playgroud)

编辑:正如评论中提到的@mgilson,patch()修改了类的方法,而不是返回一个新类.正因为如此,我们可以代替__new__这个__init__:

class PatchMeta(type):
    """A metaclass to patch all inherited classes."""

    def __init__(cls, *args, **kwargs):
        super(PatchMeta, self).__init__(*args, **kwargs)
        patch(*cls.patch_args)(cls)
Run Code Online (Sandbox Code Playgroud)

这是无可争议的清洁.