What is Liskov Principle?
In layman’s terms Liskov Substitution Principle says that if class Foo inherits
from class Bar, then you should be able to use (substitute) derived class in
every place that the base class is used. For a better definition and further
references check out The Liskov Substitution Principle by Uncle Bob.
Testing LSP with MiniTest
MiniTest has a really simple design. A test case is a class and an example
is a method of that class. After requiring minitest/autorun every
subclass of MiniTest::Unit::TestCase is instantiated and test methods are executed
one by one.
One very nice result of this design, which is kind of obvious when
you think about it, is that you can not only inherit helper methods
(eg. you
subclass ActionController::TestCase to have get, post etc) but you
may as well inherit whole examples! This is a perfect way to test LSP
because, again, you should be able to substitute base class with a
derived class.
Example
Let’s re-implement Ruby’s built-in Set class. I’ll write a test first:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | |
Note I didn’t write the exact result of Set#to_a because a
cannonical set is unordered. A Ruby 1.9 built-in Set is actually
ordered, it simply preserves the order of insertion.
A basic implementation is very easy using Hash like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
Let’s run it:
1 2 3 4 5 6 7 8 9 10 | |
Now, let’s write a SortedSet that will keep values sorted. Again let’s
write a test and run it first:
1 2 | |
1 2 3 4 5 6 7 8 9 10 | |
We now have exactly twice assertions because all test
methods have been inherited. Let’s now build a simple SortedSet class
and adjust the test, so that we actually use the derived class:
1 2 3 4 5 6 7 8 9 10 11 | |
Sure enough all tests passes and we’re now certain that a Set object can
be substituted with a SortedSet object.
Let’s also test the unique behaviour of the SortedSet. We won’t just
define test_to_a method, because we would overwrite assertions from
the base test. We’ll pick a different name instead:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Now, we could stop it right here, but you propably noticed some duplication
between test_to_a and test_to_a_sorted. Again, because we’re using
just classes and methods, we can actually write:
1 2 3 4 5 6 7 8 9 10 | |
I’m not sure if it’s that useful and you should use it, but you must agree it’s pretty neat!