问题 继承补丁类


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

代码示例:

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

class TestFunctions(BaseTest):
      #methods
      pass

修补 TestFunctions class直接工作,但修补BaseTest类不会改变其功能 some.core.function 在 TestFunctions


7439
2017-10-06 22:58


起源

这可能是你想谷歌的地方 “python元类” 并继续阅读,直到你了解它们的工作方式。 Metaclass由子类继承,装饰器只装饰它们所使用的类。 - Markus Meskanen
啊,我想我有点理解你的意思。补丁只出现在类的实例上? - sihrc
没有, patch 是一个装饰器,只接受它下面的类并装饰那个。现在任何子类都不会被装饰,它们只是普通的类。元类控制类的行为,因此可以在第一次创建类时对其进行修补。在设置了基类的元类之后,元类也可以处理子类,因此子类也将被修补。 - Markus Meskanen
您使用的是Python 2还是Python 3?这通常是要添加的相关标签。 - Markus Meskanen
谢谢(你的)信息。 Python2。我会再研究一下 - sihrc


答案:


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

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

所以类实际上是 type。 现在,我们可以继承 type 创建自定义元类(创建类的类):

class PatchMeta(type):
    """A metaclass to patch all inherited 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 BaseTest(unittest.TestCase):
    __metaclass__ = PatchMeta
    # methods

问题是这一行:

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

所以目前我们 总是 用参数装饰 "some.core.function" 和 mocked_method。 相反,你可以使它使用类的属性,如下所示:

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

然后添加 patch_args 到你的班级:

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

编辑: 正如@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)

这是无可争议的清洁。


7
2017-10-06 23:16



啊!我刚刚得出了类似的结论,但我完成了所有开头的功能 test 并嘲笑那些。你的看起来更优雅。两者都有优点/缺点吗? - sihrc
是否有可能有超过1个元类?或者我需要开始它吗? - sihrc
@sihrc为什么你想要更多没有元类?我从未遇到过这样的需求,你可能在这里做错了什么。我认为没有那么多缺点。 - Markus Meskanen
啊,我打赌我可以添加另一个元类,它接受一堆补丁来制作并生成一个可以完成所有这些补丁的PatchClass? - sihrc
啊,我想我知道混乱的起源。该 patch 装饰者不会返回 新 类。它返回包含所有方法的旧类 test 修补。我不推荐你这样做 self = patch(...)(self)  - 那简直令人困惑。我推荐的只是 patch(...)(self) 这是因为返回的值来自 patch 在课堂上运作 到位 (然后返回它,因为这是装饰者所做的)。 - mgilson


一般来说,我更喜欢做这种事情 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

只要你有足够的纪律来随时打电话 super 在你被覆盖的 setUp 方法,它应该工作得很好。


7
2017-10-06 23:12



我认为这也有其优点。我也从未见过用这种方式做过的补丁。谢谢你! - sihrc