Scenario 1
Here is a multiple level inheritance test framework, where each type Foo, Bar, Baz etc is tested in its own class, structured like this:
Foo_Test -> Foo_Lib -> Base
Bar_Test -> Bar_Lib -> Base
Baz_Test -> Baz_Lib -> Base
Scenario 1 shows the code for Foo
: the others would be the same. This works fine for tests that exercise only one type Foo
OR Bar
OR Baz
, but Base
contains elements that make it a singleton class, so this structure prevents combining, say, Foo
AND Bar
types in a single test.
Scenario 2
Here Foo_Lib
and Bar_Lib
have been converted to Free_Foo_Lib
and Free_Bar_Lib
. These no longer inherit from Base
; instead they are each initialised with same unique Base
instance. Calls to self.any_base_method(...)
are replaced by calls to self.base.any_base_method(...)
, but otherwise their code is unchanged.
Using Free_Foo_Lib
and Free_Bar_Lib
I can now write a single test that exercises Foo
and Bar
types as shown. The down side is that these converted libraries no longer fit the legacy tests in Foo_Test
and Bar_Test
.
I still want to support these legacy tests, without modifying them. They access functions from their respective libraries and from Base
via self
, invoking self.any_lib_method(...)
or self.any_base_method(...)
. Since I have broken their supporting inheritance chains, I need to replace them with something that looks (to them) the same.
Scenario 3
Here New_Foo_Test
inherits from New_Foo_Lib
instead of from Foo_Lib
, but the tests within it are unchanged from the old Foo_Test
.
New_Foo_Lib
has become a wrapper for the methods in Free_Foo_Lib
and Base
, inheriting from both classes, calling their respective __init__
methods within its own. To do this, New_Foo_Lib.__init__
first calls Base.__init__(self)
, then 'bootstraps' itself to supply the second, my_base
, parameter needed by Free_Foo_Lib.__init__
.
Running the sample code confirms that foo_test()
in Scenario 3 behaves the same as foo_test()
in Scenario 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()
Output
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
Summary
This is what I want to be able to do, to 're-plumb' the legacy test cases with flexible libraries which aren't constrained by inheritance from Base
. So what is my question? It's about the 'bootstrap' initialiser:
Free_Foo_Lib.__init__(self, self)
I've googled for python
__init__(self, self)
but not found it. So is it a part of any recognised pattern?If not, is there a recognised pattern that could be used instead, which (a) keeps the legacy tests unchanged, (b) makes only minimal changes to the libraries, but enables (c) testing Foo and Bar together?
Looking at the output of scenario 3,
New_Foo_Lib
containsNew_Foo_Test
as its base. This apparent inversion seems confusing, but would there actually be any undesirable side effects from it? (My experience with it suggests not.)