The subject of testing abstract classes came up recently. Imagine the following scenario – I have a class @AbstractBob@ which provides an implementation of a method @callFred@. @ConcreteBob@ extends @AbstractBob@ and implements the required methods – at this stage it doesn’t override @callFred@. Now what would you test? There are several approaches I can think of:
# Just test the concrete class
# Test the methods defined in the abstract class by creating a stub implementation, then test the methods explicitly implemented in the concrete class but not those implemented in the abstract.
# Create an abstract @TestCase@ (or whatever – depends on your testing tool) which tests those methods implemented in the abstract class. Then, subclass for testing your concrete class
Looking at the first solution, in our trivial example this is probably acceptable. However, what if at a later point @ConcreteBob@ overrides @AbstractBob@’s implementation of @callFred@? Now we are still testing @ConcreteBob@, but do not have test coverage for @AbstractBob@’s implementation any more. If nothing else extends @AbstractBob@ this might not be a problem (but then why define the abstract class in the first place?). If other things extend @AbstractBob@ but define their tests in the same way, again there isn’t a problem (although in such a situation you may have a heavy overlapping of test coverage). If however you are shipping an API and have defined the abstract class with a view to it being subclasses by end users, then the abstract class itself needs test coverage.
Lets look at scenario 2. In our trivial example we again get full test coverage assuming the implementation of @callFred@ isn’t overridden. We also ensure that we are directly testing the abstract class, giving us some confidence that our end users can happily extend the class. However the moment @ConcreteBob@ changes @callFred@ we loose test coverage.The obvious solution here is to retest @callFred@ in @ConcreteBob@ – and this is probably something that you should do as a matter of course when changing an abstract classes implementation in your concrete class (in fact you should of written the test _first_).
Scenario 3 is really a shorthand way of doing scenario 2.
The difficulties associated with testing abstract classes may in fact lead you to decide to extract objects rather than abstract classes – for example @callFred@ might just delegate to a support object capable of making the call, which can be tested in isolation. In the end I’m not sure there is a hard and fast rule for this – and in most cases the use of a decent code coverage tool should pick up the shortcomings of any particular approach. So go forth and start using your code coverage tool of choice, and run it every time you run your tests – you’ll start seeing the benefits of different testing approaches, and will get a warm feeling every time the test coverage moves up a notch…