Practical Testing: 9 - More tests, more development, notice the order?

Previously, on Practical Testing: we fought through the pain of writing tests for hard to test code and then we decided to see what it could have been like if we’d developed the code test first… Now we’ll add some more tests and some more code, still keeping it all nice and simple…

In our BOT development our second test was a test for multiple timers. Unfortunately that will completely break our simplest possible implementation of a timer queue so we’ll ignore that test for now. I know it seems like cheating, but we’ll write the tests for cancelling a timer and then for proving that we don’t have a problem when the tick count wraps around. We’ll leave the multiple timer test for later on. The reason for this, apart from the fact that it’s obvious that our current implementation would fail miserably, is that when we finally come to write the code to make the multiple timer test pass we’ll have two other tests to help keep us on the right track…

So, we’ll write a test to cancel a timer. To be able to cancel a timer we need some way of identifying the timer that we want to cancel. We could use the instance of the Timer that we pass in to SetTimer() but that would mean that each instance of Timer could only be used once, and, given we can pass ‘user data’ with each call to SetTimer() I can see a usage pattern that allows us to use the same instance of Timer with multiple different timeouts, all set at once… So, rather than declaring CancelTimer() like this:

void CancelTimer(const Timer &timer);

We’ll adjust SetTimer() and then declare it like this:

Handle SetTimer(
   Timer &timer,
   const DWORD timeoutMillis,
   const UserData userData);
bool CancelTimer(
   Handle handle);

We now have an opaque handle that we return to the caller of SetTimer() and it is that handle that they must pass to subsequent calls to CancelTimer(). This differs to our original implementation because our Handle is simply a typedef’d type that can hold a pointer value. Originally we’d had a massively complex, reference counted, handle/body, node wrapping, monstrosity; and what’s more it doubled as the timer we set…

We need to adjust SetTimer() to return the Handle and, in the spirit of the simplest thing that could possibly work, we simply cast our Timer to a Handle and return that.

Now we can write a test for CancelTimer().

void CCallbackTimerQueueTest::TestCancelTimer()
   const _tstring functionName = _T("CCallbackTimerQueueTest::TestCancelTimer");
   Output(functionName + _T(" - start"));
   CMockTickCountProvider tickProvider;
   CCallbackTimerQueue timerQueue(tickProvider);
   THROW_ON_FAILURE(functionName, INFINITE == timerQueue.GetNextTimeout());
   CLoggingCallbackTimer timer;
   CCallbackTimerQueue::Handle handle = timerQueue.SetTimer(timer, 100, 1);
   tickProvider.CheckResult(_T("|GetTickCount: 0|"));
   THROW_ON_FAILURE(functionName, 100 == timerQueue.GetNextTimeout());
   tickProvider.CheckResult(_T("|GetTickCount: 0|"));
   THROW_ON_FAILURE(functionName, true == timerQueue.CancelTimer(handle));
   THROW_ON_FAILURE(functionName, INFINITE == timerQueue.GetNextTimeout());
   THROW_ON_FAILURE(functionName, false == timerQueue.CancelTimer(handle));
   THROW_ON_FAILURE(functionName, false == timerQueue.CancelTimer(0));
   THROW_ON_FAILURE(functionName, INFINITE == timerQueue.GetNextTimeout());
   Output(functionName + _T(" - stop"));

Something like this perhaps, we set a timer, cancel it and then try cancelling it again and cancelling random other values. If the timer was pending when we cancelled it then CancelTimer() will return true otherwise it’ll return false.

Implementing CancelTimer() in our simplest world is easy, if a timer is set and the handle is the correct handle for the timer that is set, unset the timer and return true, else, return false.

Test passes, we have some more functionality and the design is evolving. Time for another test, this time we’ll test for the tick count wrap problem. We’ll set the world up to be 1000 milliseconds before rollover, set a timer for 1100 and then check that the correct values are reported and the right things happen as we move forward in time under control of the test.

Unfortunately our current implementation fails this test, we’ll fix that next time. Code is here. Same rules as before.