Package: liskov
Utility to check if your subtypes pass supertypes tests.Liskov Substitution is related in SOLID principles.
It has been formulated by Barbara Liskov and Jeanette Wing
in order to define more precisely the notion of subtypes.For more details read:http://reports-archive.adm.cs.cmu.edu/anon/1999/CMU-CS-99-156.psTo ensure a certain respect of Liskov Substitution principle in your program,
you can simply make your subtypes tests extends your supertypes tests, but
importing a supertype test directly in subtype file make the test
being recognized by unit tests runners and run it several times.You can just import it in a function, but to not repeat this every time,
I’ve made a small util, wich offers 3 ways to declare a subtype test.Each solution gives you different expressiveness depending on your preference:“subtype” function which just import and return a class from a module given as string argument.“behave_as” metaclass generator function which returns a metaclass from given modules.“can_substitute” decorator wich returns the class extending modules given as arguments.Liskov and Wing classified subtype relationships in two broad categories:Extension subtypes: add methods or eventually states to supertypesConstrained subtypes: when supertype enable variations in subtypesSince version 0.2 you can find some helpers to define constraints.(see example 4)Table of ContentsInstallationUsageDevelopmentInstallationSimply install it from pypi:pip install liskovor from sources:git clone git@github.com:apieum/liskov.git
cd liskov
python setup.py installUsageExample 1 - “subtype”:Use a lambda if too long.fromliskovimportsubtypeBasicCalc=lambda:subtype('testCalc.BasicCalcTest')BaseConverter=lambda:subtype('testConvert.BaseConverterTest')classScientificCalcTest(BasicCalc(),BaseConverter()):deftest_it_is_a_subtype_of_BasicCalc(self):fromtestCalcimportBasicCalcTestassertisinstance(self,BasicCalcTest)deftest_it_is_a_subtype_of_BaseConverter(self):fromtestConvertimportBaseConverterTestassertisinstance(self,BaseConverterTest)Example 2 - “behave_as”:Python 2 versionfromliskovimportbehave_asclassScientificCalcTest(object):__metaclass__=behave_as('testCalc.BasicCalcTest','testConvert.BaseConverterTest')deftest_it_is_a_subtype_of_BasicCalc(self):fromtestCalcimportBasicCalcTestassertisinstance(self,BasicCalcTest)deftest_it_is_a_subtype_of_BaseConverter(self):fromtestConvertimportBaseConverterTestassertisinstance(self,BaseConverterTest)Python 3 versionfromliskovimportbehave_asmetaclass=behave_as('testCalc.BasicCalcTest','testConvert.BaseConverterTest')classScientificCalcTest(object,metaclass=metaclass):deftest_it_is_a_subtype_of_BasicCalc(self):fromtestCalcimportBasicCalcTestassertisinstance(self,BasicCalcTest)deftest_it_is_a_subtype_of_BaseConverter(self):fromtestConvertimportBaseConverterTestassertisinstance(self,BaseConverterTest)Example 3 - “can_substitute”:fromliskovimportcan_substitute@can_substitute('testCalc.BasicCalcTest','testConvert.BaseConverterTest')classScientificCalcTest(object):deftest_it_is_a_subtype_of_BasicCalc(self):fromtestCalcimportBasicCalcTestassertisinstance(self,BasicCalcTest)deftest_it_is_a_subtype_of_BaseConverter(self):fromtestConvertimportBaseConverterTestassertisinstance(self,BaseConverterTest)Example 4 - Constraints:This example follow Liskov and Wing constrained subtypes Elephants hierarchy example
from “Behavioural Subtyping using invariants and constraints” (link above)Elephants can be white, green or blue
RoyalElephant is always blue
AlbinoElephant is always whiteEach instance of Elephant in ElephantTest is made with “new_elephant”
ElephantTest test if an Elephant can be white, green or blue.Declare Constraints with a decoratorfromliskovimportcan_substitute,under_constraintimportelephant@can_substitute('elephant.ElephantTest')@under_constraint('test_it_can_be_grey','test_it_can_be_white')classRoyalElephantTest(object):defnew_elephant(self,*args):returnelephant.RoyalElephant()Declare Constraints with metaclassPython 2 versionfromliskovimportbehave_asimportelephantclassRoyalElephantTest(object):__metaclass__=behave_as('elephant.ElephantTest').except_for('test_it_can_be_grey','test_it_can_be_white')defnew_elephant(self,*args):returnelephant.RoyalElephant()Python 3 versionfromliskovimportbehave_asimportelephantmetaclass=behave_as('elephant.ElephantTest').except_for('test_it_can_be_grey','test_it_can_be_white')classRoyalElephantTest(object,metaclass=metaclass):defnew_elephant(self,*args):returnelephant.RoyalElephant()Declare Constraints with subtype functionbind “subtype” to “constrain” with any of these operators: “& | + -”fromliskovimportsubtype,constrainimportelephantConstrainedElephantTest=lambda:subtype('elephant.ElephantTest')&constrain('test_it_can_be_grey','test_it_can_be_white')classRoyalElephantTest(ConstrainedElephantTest()):defnew_elephant(self,*args):returnelephant.RoyalElephant()DevelopmentFell free to give feedback or improvment.Launch test:git clone git@github.com:apieum/liskov.git
cd liskov
nosetests --with-spec --spec-color