Practical Testing: 8 - Once more, with tests first
I’ve been writing some blog entries about a piece of code from my ‘back catalogue’ that didn’t have tests and that had a known bug that was reasonably hard to test for. Right at the start I commented that the code was a tad over complicated due to the way it had been developed using HITIW (Hack it till it works). The complexity in the code itself made writing tests for it harder than it should have been and, well, I wouldn’t write code like that these days (honest). So, here we are in part 8 and I’m about to throw the first version away and write a new version of the code and this time we’ll do it test first and take a look at how the desire to be able to test the code shapes the design. I know you’re probably thinking that this is just a bit contrived but I really did just sit down with an empty file and the tests that I had from last time and write the simplest thing that could work to pass the first test…
The first thing we do is delete all of the code that we were using last time, even the tests. We’ll keep the spirit of the tests in mind, but we’ll allow them to develop in their own way and write the code to fit. We’ll see, later on, that the order that we develop the tests in is likely to be different and the code we write to make those tests pass is going to be as simple as we can get. So, forget about threads, forget about ridiculously complex mock objects who’s only purpose in life is to provide a DWORD
to their caller and forget about the mess that was the CCallbackTimer
class.
What do we need the class to do? Well, we need to be able to set a timer for a number of milliseconds in the future and have that class that is responsible for these timers manage the timer that we’ve set and signal it when it is time. First we’ll pick some names; the thing that manages the timers will be CCallbackTimerQueue
and the timers themselves will be Timer
, a nested class of the timer manager. We know from our previous experience that we’ll want to be able to provide the timer manager with a source for its time so that we can replace that source with a mock for testing. We’ll ignore, for a moment, how we’ll make the timer manager automatically handle our timers and instead think about the interface that we would require to be able to write some code that drives it; we’d need to know when the next timer is scheduled for and we’d need to be able to tell it to deal with any timeouts that have occurred now… So, with that in mind, let’s write a test…
void CCallbackTimerQueueTest::TestTimer()
{
const _tstring functionName = _T("CCallbackTimerQueueTest::TestTimer");
Output(functionName + _T(" - start"));
CMockTickCountProvider tickProvider;
CCallbackTimerQueue timerQueue(tickProvider);
tickProvider.CheckResult(_T("|"));
THROW_ON_FAILURE(functionName, INFINITE == timerQueue.GetNextTimeout());
tickProvider.CheckResult(_T("|"));
CLoggingCallbackTimer timer;
timerQueue.SetTimer(timer, 100, 1);
tickProvider.CheckResult(_T("|GetTickCount: 0|"));
// Prove that time is standing still
THROW_ON_FAILURE(functionName, 100 == timerQueue.GetNextTimeout());
tickProvider.CheckResult(_T("|GetTickCount: 0|"));
timerQueue.HandleTimeouts();
tickProvider.CheckResult(_T("|GetTickCount: 0|"));
timer.CheckResult(_T("|"));
THROW_ON_FAILURE(functionName, 100 == timerQueue.GetNextTimeout());
tickProvider.CheckResult(_T("|GetTickCount: 0|"));
tickProvider.SetTickCount(100);
THROW_ON_FAILURE(functionName, 0 == timerQueue.GetNextTimeout());
tickProvider.CheckResult(_T("|GetTickCount: 100|"));
timerQueue.HandleTimeouts();
tickProvider.CheckResult(_T("|GetTickCount: 100|"));
timer.CheckResult(_T("|OnTimer: 1|"));
THROW_ON_FAILURE(functionName, INFINITE == timerQueue.GetNextTimeout());
tickProvider.CheckResult(_T("|"));
Output(functionName + _T(" - stop"));
}
As expected, it doesn’t compile (well, it’s a piece of html, what do you expect!) so we write the timer queue class with stubbed out functions and implement the mocks, yada, yada, yada. Note we can do a really simple mock tick count provider that just returns a number :)
Of course, the simplest possible implementation to make this test pass is one that doesn’t contain a queue of timers at all, avoiding the obvious “just hard code it all” step 1, we can still take a pretty small step and just have the timer queue store our single timer details and the test will pass…
Not surprisingly, the code is really simple. We have no threads, no lists, no complicated tick count wrap work around, just a nice simple, pretty useless piece of code that passes our first test. Well, it’s a start. Next time we’ll add some more tests and start making the code a little more real.