Last week I had the chance to attend Jason Gorman’s workshop on SOLID, a set of principles for good object-oriented design. If you want to find out whether your code conforms to one of these five principles (the L, specifically) then there is a nifty trick for testing it easily, and I think it’s worth sharing.
What is the Liskov Substitution Principle?
At the risk of paraphrasing Wikipedia it states that if a class S is a subtype of T then objects of type T may be replaced with objects of type S (i.e., objects of type S may be substituted for objects of type T), without altering any of the desirable properties of that program. Or to put it simply, your subclasses should retain all the properties of their parent classes.
In Java the clue is in the name – the keyword for subclassing is extends. A subclass should ideally add extra properties but not diverge from the behaviour of the parent class by overriding methods. If the subclass behaves in a significantly different way to the parent class when presented with the same input then that might indicate that it shouldn’t really be a subclass at all.
(Disclaimer: like most design principles this is a general rule of thumb and does not apply in every case)
So how do you test it?
Here’s a simple project with a class that represents a horse (the original example used bank accounts but horses are more interesting) and an associated unit test class:
And here is the code for the test class:
The key point to notice about this test class is that it includes a factory method getHorse() which returns an instance of the class to test. The factory method is protected rather than private for reasons that will become obvious.
When you run the tests they all pass.
Now imagine that you extend this project with a class RaceHorse which is a subclass of Horse. The new class might include some extra methods specific to racing, for example getting the number of races won, but it’s still a horse and should retain all the behaviour of the parent class. In other words it should be Liskov substitutable.
The project now looks like this:
We will need a test for the extra functionality provided by the RaceHorse class. Here is the code listing:
The trick that makes this technique work is to make RaceHorseTest extend HorseTest, and override the factory method to return an instance of the subclass RaceHorse rather than Horse. Here’s what happens when you run the tests:
As you can see JUnit has run not just the new test for RaceHorse but all of the tests defined in the parent test class as well. This is basically a free safety net, if all the tests pass then you have a good indicator that your subclass is Liskov substitutable with the parent.
The nice thing about this is that before you write a single line of code in RaceHorse you already have a comprehensive set of tests for it, which is a confidence boost for when you start implementing the new functionality. Another cool thing about this technique is that the substitutability safety net automatically grows as you change the base class – if you add new tests for the base class they will also be run against the subclass with no effort required on your part.
If only guaranteeing conformity with the rest of the design principles was this easy!