继承补丁 class

Inheriting a patched class

我有一个基础 class 扩展 unittest.TestCase,我想修补那个基础 class,这样 class 就可以扩展这个基础 class也会应用补丁。

代码示例:

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

class TestFunctions(BaseTest):
      #methods
      pass

直接给 TestFunctions class 打补丁,但是给 BaseTest class 打补丁不会改变 TestFunctionssome.core.function 的功能。

一般来说,我更喜欢在 setUp. You can make sure that the patch gets cleaned up after the test is completed by making use of the tearDown method (or alternatively, registering a the patch's stop method with 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

只要您有足够的纪律性,总是在覆盖的 setUp 方法中调用 super,它应该可以正常工作。

您可能需要一个 metaclass:metaclass 简单地定义了 class 的创建方式。 默认情况下,所有 classes 都是使用 Python 的内置 class type:

创建的
>>> class Foo:
...     pass
...
>>> type(Foo)
<class 'type'>
>>> isinstance(Foo, type)
True

所以 classes 实际上是 type 的实例。 现在,我们可以通过 subclass type 创建一个自定义元 class (一个 class 创建 classes):

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

我们需要控制 classes 的创建,所以我们想在这里覆盖 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

现在您只需使用 __metaclass__ = PatchMeta:

设置元class
class BaseTest(unittest.TestCase):
    __metaclass__ = PatchMeta
    # methods

问题出在这一行:

cls = patch("some.core.function", mocked_method)(cls)

所以目前我们总是用参数"some.core.function"mocked_method装饰。 相反,你可以让它使用 class 的属性,像这样:

cls = patch(*cls.patch_args)(cls)

然后将 patch_args 添加到您的 classes:

class BaseTest(unittest.TestCase):
    __metaclass__ = PatchMeta
    patch_args = ("some.core.function", mocked_method)

编辑: 正如@mgilson 在评论中提到的,patch() 修改了 class 的方法,而不是返回一个新的 class。因此,我们可以用 __init__:

替换 __new__
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)

这无疑是更干净的。