Practical Testing: 24 - Removing test duplication


The most recent articles in the "Practical Testing" series have been discussing the performance of the timer queue that we have built. Once I had got some new, optional, performance tests in place to measure what we were trying to improve I eventually came up with a new approach and began to implement a timer wheel that conforms to the interface used by the other implementations of my timer queue. Whilst doing this it became obvious that there was duplication in my test code and so the tests have been refactored to remove the duplication of the test code between CCallbackTimerQueue and CCallbackTimerQueueEx and, in addition, to create a full suite of tests for the new CCallbackTimerWheel class. This puts us firmly in Test Driven Development as we now have a set of tests where most of them fail for the new implementation.

To make sure the tests fail in a reliable way I've gone through the new CCallbackTimerWheel class and added exceptions which are thrown whenever some of the 'currently not implemented' functionality is exercised.

Removing the duplication was fairly straight forward. A new template class has been created and this contains the tests which are common between the different implementations. The class it derived from by the concrete tests classes for each implementation class and is templatised on both the class under test, the tick count provider and a 'traits' class that I use to tell the tests about various differences in behaviour. The traits are useful to paper over the slight differences which are exposed due to the slightly over invasive way that I like to test...

There's a school of thought that says the way I test leads to brittle tests because I often validate the calls made into my mock objects, the tick count provider, for example, to make sure that the expected sequence of calls happens. Of course this ties my tests to my current implementation. I can understand why this is often a bad thing in that I could change my implementation to improve things and whilst the new implementation does what it should to pass a test it might fail due to the interaction changes that I've made. I agree, it's a pain to have to go and change a bunch of tests because you have changed the number of calls into one of your mocks but I prefer this to not testing these interactions and suddenly finding that our code is calling an expensive call multiple times due to laziness on the part of the developer (me) or that we're suddenly not using an interface that we've provided with and nobody was aware of it... Anyway, the three implementations of the timer queue all have slightly different interactions with their tick count providers and these differences are captured in the test traits which allows the tests to be invasive correctly for each implementation...

Another problem was the slight constructor parameter differences between the new timer wheel and the older queues. The wheel needs to know the maximum timeout range that it supports and the timer granularity and the queues don't. To get around this I've used a very thin shim class which simply defaults the timer wheel's parameters for the shared tests.

Since the calculation of expected timeouts needed to change in the shared tests due to the timer wheel's granularity settings the tests could now work with timer queues that support a timer granularity other than 1. This may be a simple performance tweak and is something that's now on the list of things to do...

Whilst I've removed the blatant copy and paste nature of the duplicate tests there's still plenty of scope to refactor them to reduce the small scale duplication that's going on; that, however, is a job for another day.

The code can be found here and the previous rules apply.

Leave a comment