I would like to write some test in a way that are executed for all classes that inherit from a Parent.
For example I have the class motor with two specializations:
class Motor():
def run(self, energy):
pass
class ElectricMotor(Motor):
def run(self, electric_energy):
heat = electric_energy * 0.99
motion = electric_energy * 0.01
return heat, motion
class DieselMotor(Motor):
def run(self, diesel_energy):
heat = diesel_energy * 0.65
motion = diesel_energy * 0.35
return heat, motion
Then I have two tests which apply to every kind of motor:
class MotorTest(unittest.TestCase):
def test_energy_should_be_conserved():
for class_instance in all_motor_child_classes:
energy=10
assert sum(class_instance.run(energy))==energy
energy=20
assert sum(class_instance.run(energy))==energy
def test_motors_should_produce_heat():
for class_instance in all_motor_child_classes:
energy = 10
heat, motion=class_instance.run(energy)
assert heat>0
What I'm looking for is a way to do the loop
for class_instance in all_motor_child_classes:
or a different programming pattern to obtain the same result.
Any idea? Thanks Riccardo
Well, there are two points here: first having a list of Motor
child classes, then having an instance of each of those classes.
The stupid simple solution is to maintain those lists in your testcase's setUp
:
from motors import ElectricMotor, DieselMotor
class MotorTest(unittest.TestCase):
_MOTOR_CHILD_CLASSES = [ElectricMotor, DieselMotor]
def setUp(self):
self.motor_child_instances = [cls() for cls in self._MOTOR_CHILD_CLASSES]
def test_energy_should_be_conserved():
for class_instance in self.motor_child_instances:
self.assertEqual(sum(class_instance.run(10)), 10)
# etc
If your Motor
subclasses __init__()
expect different arguments (which they shouldn't if you want to have proper subtyping according to liskov substitution principle - but well, "practicality beats purity"), you can add those arguments to your MOTOR_CHILD_CLASSES
list:
# (cls, args, kw) tuples
_MOTOR_CHILD_CLASSES = [
(ElectricMotor, (42,), {"battery":"ioncad"}),
(DieselMotor, (), {"cylinders":6}),
]
and use them in the setUp()
:
self.motor_child_instances = [
cls(*args, **kw) for cls, args, kw in self._MOTOR_CHILD_CLASSES
]
For something more "automagic", you can use a custom metaclass on Motor
so it can registers its subclasses and provide a list of them, but then you'll loose the ability to provide per-class arguments - and you'll also make your tests code much less readable and predicable.
Now another - and IMHO much better - approach is to use inheritance in your tests instead: define a mixin object with all the tests that are common to all Motor
child classes :
class MotorTestMixin(object):
# must be combined with a unittest.TestCase that
# defines `self.instance` as a `Motor` subclass instance
def test_energy_should_be_conserved(self):
self.assertEqual(sum(self.instance.run(10)), 10)
def test_should_produce_heat(self):
heat, motion = self.instance.run(10)
self.assertGreater(heat, 0)
then have one TestCase per subclass:
class DieselMotorTest(MotorTestMixin, TestCase):
def setUp(self):
self.instance = DieselMotor()
class ElectricMotorTest(MotorTestMixin, TestCase):
def setUp(self):
self.instance = ElectricMotor()
One of the benefits of this approach (the others being simplicity, readability, and a much better error reporting on failed tests - you'll immediatly know which subclass failed without having anything special to do) is that you don't have to touch your existing code when you add a new Motor
subclass - you just need to add a new individual TestCase for it -, and you can even do so in a distinct module, following the open/closed principle.