Преобразование в структуру на основе композиции без потери наследства - PullRequest
0 голосов
/ 25 мая 2019

Сценарий 1

Вот многоуровневая среда тестирования наследования, где каждый тип Foo, Bar, Baz и т. Д. Тестируется в своем собственном классе, структурированном так:

Foo_Test -> Foo_Lib -> Base

Bar_Test -> Bar_Lib -> Base

Baz_Test -> Baz_Lib -> Base

Сценарий 1 показывает код для Foo: остальные будут такими же. Это хорошо работает для тестов, которые выполняют только один тип Foo ИЛИ Bar ИЛИ Baz, но Base содержит элементы, которые делают его одноэлементным классом, поэтому эта структура предотвращает объединение, скажем, Foo И Bar вводит в одном тесте.

Сценарий 2

Здесь Foo_Lib и Bar_Lib были преобразованы в Free_Foo_Lib и Free_Bar_Lib. Они больше не наследуются от Base; вместо этого каждый из них инициализируется одним и тем же уникальным экземпляром Base. Звонки на self.any_base_method(...) заменяются звонками на self.base.any_base_method(...), но в остальном их код не изменяется.

Используя Free_Foo_Lib и Free_Bar_Lib, теперь я могу написать один тест, который работает с типами Foo и Bar, как показано. Недостатком является то, что эти преобразованные библиотеки больше не соответствуют устаревшим тестам в Foo_Test и Bar_Test.

Я все еще хочу поддержать эти устаревшие тесты, не изменяя их. Они получают доступ к функциям из своих соответствующих библиотек и из Base через self, вызывая self.any_lib_method(...) или self.any_base_method(...). Поскольку я порвал их поддерживающие цепочки наследования, мне нужно заменить их чем-то, что выглядит (для них) одинаково.

Сценарий 3

Здесь New_Foo_Test наследуется от New_Foo_Lib вместо Foo_Lib, но тесты в нем не отличаются от старых Foo_Test.

New_Foo_Lib стал оберткой для методов в Free_Foo_Lib и Base, наследуя от обоих классов, вызывая их соответствующие __init__ методы внутри своих собственных. Для этого New_Foo_Lib.__init__ сначала вызывает Base.__init__(self), а затем сам загружается, чтобы предоставить второй, my_base, параметр, необходимый для Free_Foo_Lib.__init__.

Запуск примера кода подтверждает, что foo_test() в Сценарии 3 ведет себя так же, как foo_test() в Сценарии 1.

class Base(object):
    def __init__(self):
        print('Only 1 Base instance allowed')

    def base_method(self):
        print("base method")

# Scenario 1: Original multiple level inheritance, for foo tests
class Foo_Lib(Base):

    def lib_method(self):
        print("foo method")

    def lib_calling_base(self):
        print("foo method calls base")
        self.base_method()

class Foo_Test(Foo_Lib):

    def foo_test(self):
        self.base_method()
        self.lib_method()
        self.lib_calling_base()

# Scenario 2: Multi-type foo, bar test using composition
class Free_Foo_Lib(object):
    def __init__(self, my_base):
        self.base = my_base
        print("Free_Foo_Lib has base {}".format(my_base))

    def lib_method(self):
        print("foo method")

    def lib_calling_base(self):
        print("foo method calls base")
        # Calls to base must now be via self.base, not self
        self.base.base_method()

class Free_Bar_Lib(object):
    def __init__(self, my_base):
        self.base = my_base
        print("Free_Bar_Lib has base {}".format(my_base))

    def lib_method(self):
        print("bar method")

class Foo_Bar_Test(object):

    def foo_bar_test(self):
        self.base = Base()
        self.foo_lib = Free_Foo_Lib(self.base)
        self.bar_lib = Free_Bar_Lib(self.base)
        self.base.base_method()
        self.foo_lib.lib_method()
        self.foo_lib.lib_calling_base()
        self.bar_lib.lib_method()

# Scenario 3:  Foo tests still see inheritance, but
# now supported by composition lib 'under the hood'
class New_Foo_Lib(Base, Free_Foo_Lib):
    def __init__(self):
        Base.__init__(self)
        Free_Foo_Lib.__init__(self, self)

class New_Foo_Test(New_Foo_Lib):

    def foo_test(self):
        self.base_method()
        self.lib_method()
        self.lib_calling_base()

print('Scenario 1: Original foo tests')
x = Foo_Test()
x.foo_test()
print('')
print('Scenario 2: Multi-type foo, bar test')
y = Foo_Bar_Test()
y.foo_bar_test()
print('')
print('Scenario 3: New foo tests run as original')
z = New_Foo_Test()
z.foo_test()

выход

Scenario 1: Original foo tests
Only 1 Base instance allowed
base method
foo method
foo method calls base
base method

Scenario 2: Multi-type foo, bar test
Only 1 Base instance allowed
Free_Foo_Lib has base <__main__.Base object at 0x0000000003456E10>
Free_Bar_Lib has base <__main__.Base object at 0x0000000003456E10>
base method
foo method
foo method calls base
base method
bar method

Scenario 3: New foo tests run as original
Only 1 Base instance allowed
Free_Foo_Lib has base <__main__.New_Foo_Test object at 0x0000000003456F60>
base method
foo method
foo method calls base
base method

Резюме

Это то, что я хочу сделать, чтобы «перенастроить» устаревшие тестовые случаи с гибкими библиотеками, которые не ограничены наследованием от Base. Так в чем мой вопрос? Это про инициализатор 'bootstrap':

Free_Foo_Lib.__init__(self, self)

  1. Я гуглил для python __init__(self, self), но не нашел его. Так это часть какого-либо признанного паттерна?

  2. Если нет, есть ли распознанный шаблон, который можно использовать вместо этого, который (а) сохраняет прежние тесты без изменений, (б) вносит только минимальные изменения в библиотеки, но позволяет (в) тестировать Foo и Bar вместе

  3. Если посмотреть на выходные данные сценария 3, New_Foo_Lib содержит New_Foo_Test в качестве базы. Эта очевидная инверсия кажется сбивающей с толку, но будут ли какие-либо нежелательные побочные эффекты от нее? (Мой опыт показывает, что нет.)

...