Testing Pattern: Factory Method on Base Test Class
Jason Gorman blogged about the Reused Abstraction Principle. I then asked this of him on Twitter:
@jasongorman when I've discovered one and pushed it down into a base class, what should I do with tests for derived class that exercise it?
Jason was kind enough to engage me and expand on this:
@hotgazpacho Maintain your tests :) I would create a test base class that tests the abstracted behaviour (there's a cool pattern for that)
@hotgazpacho In your base test class, define a method that creates/build object under test which you can override in test subclasses :)
@hotgazpacho That way you can dynamically bind specialised classes to the base class's tests - Liskov Substution
So, I gave it a try. And you know what? Jason was right! This is a really cool pattern! A number of people on twitter have urged me to blog about it (ok, I asked them to nag me), and now I've finally gotten around to doing it.
Liskov Substitution Principle
The mechanics behind this technique derive from the Liskov Substitution Principle (LSP), which, boiled down, states that
Derived classes must be substitutable for their base classes. The classic example of a design that violates LSP is a Square that derives from a Rectangle. Both are quadrilaterals, but the Square has an added constraint that the width and the height must be equal. Rectangles do not have this restriction. So, if you were to substitute an instance of a Square for an instance of a Rectangle, then tried to set the width and height to unequal values, you would break the Square. A Square is NOT substitutable for a rectangle, as the behaviors of setting the dimensions differ.
Factory Method on Base Test Class
So, if we have properly abstracted our reusable behavior to a base class, we can (and should!) do the same for the tests surrounding this behavior. In our base test class, we create a factory method that returns an instance of the base class. When we create our derived classes, their test classes inherit from the base test class, and override the factory method to produce an instance of the derived class. Let's look at an example.
I work primarily on the .NET platform in C# using NUnit. Pardon the ridiculousness of the example, but it illustrates the point.
In the CollectionViewBase class, we define some tests that every sub-class of CollectionViewBase should pass. The tests for the specialized classes inherit from the tests for the base class, and overrides the GetView() method to provide an instance of the specialized class. NUnit will pick up the tests defined in the base test class and, through the magic of inheritance, run them as part of the fixture for the specialized classes.
Following this pattern, you've now accomplished the following:
- Your classes cannot violate LSP.
- You've reduced the amount of test code you have to maintain.
- You have basic, instant code coverage for each specialization of the base class.