Сценарий 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)
Я гуглил для python __init__(self, self)
, но не нашел его. Так это часть какого-либо признанного паттерна?
Если нет, есть ли распознанный шаблон, который можно использовать вместо этого, который (а) сохраняет прежние тесты без изменений, (б) вносит только минимальные изменения в библиотеки, но позволяет (в) тестировать Foo и Bar вместе
Если посмотреть на выходные данные сценария 3, New_Foo_Lib
содержит New_Foo_Test
в качестве базы. Эта очевидная инверсия кажется сбивающей с толку, но будут ли какие-либо нежелательные побочные эффекты от нее? (Мой опыт показывает, что нет.)