继承一个修补过的类

时间:2015-10-06 22:58:02

标签: python python-2.7 python-unittest python-mock

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

代码示例:

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

class TestFunctions(BaseTest):
      #methods
      pass

直接修补TestFunctions类,但修补BaseTest类不会改变some.core.functionTestFunctions的功能。

2 个答案:

答案 0 :(得分:7)

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

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,那么它应该可以正常工作。

答案 1 :(得分:7)

您可能需要一个元类:元类只是定义了如何创建类。 默认情况下,所有类都是使用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)

这无可争议地更清洁。