Sam Newman's site, a Consultant at ThoughtWorks

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…


4 Responses to “Testing abstract classes”

  1. Simon Brunning

    It’s *got* to be solution one – test only the concrete class(es).

    The abstract class is just an implementation detail. Any functionality that it exposes that gets used by the concrete classes will be tested by their unit tests. Any functionality that *doesn’t* get used doesn’t need to be *tested*, it needs to be refactored out. YAGNI.

  2. Sam Newman

    But what about the situation when you know developers will be extending your Abstract class? Without code coverage of its methods (assuming your concrete implementations override at least part of its behaviour) how can you make sure it really works?

  3. ade

    You seem to be looking for the AbstractTestCase pattern as documented here:

    You can also use the same technique for testing conformance to interfaces.

    If I ever get around to releasing my nu-collections library you can see it used extensively there. Sam, can of course, just ask me to show him the code the next time we see each other.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Basic HTML is allowed. Your email address will not be published.

Subscribe to this comment feed via RSS

%d bloggers like this: