Подтвердить что ты не робот

Наследование исправленного класса

У меня есть базовый класс, расширяющий unittest.TestCase, и я хочу исправить этот базовый класс, так что классы, расширяющие этот базовый класс, будут иметь также исправления.

Пример кода:

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

class TestFunctions(BaseTest):
      #methods
      pass

Задание класса TestFunctions напрямую работает, но исправление класса BaseTest не изменяет функциональность some.core.function в TestFunctions.

4b9b3361

Ответ 1

Вероятно, вам нужен метакласс: метакласс просто определяет, как создается класс. По умолчанию все классы создаются с использованием встроенного класса 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)

Это совершенно беспроблемно.

Ответ 2

Как правило, я предпочитаю делать такие вещи в setUp. Вы можете убедиться, что исправление будет очищено после завершения теста, используя метод tearDown (или, альтернативно, зарегистрировав patch 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, он должен работать нормально.