My Unicode Byte Order Mark hassles yesterday were brought to light by one of my mock objects. It was the expected output log for the object that had been converted from UTF-16 to UTF-8 by CVS without asking… Whilst writing that piece I realised that I probably needed to write this piece. You see, I spend most of my time in C++ and I write most of my Mock objects myself rather than having some framework do it for me and something that I found very useful and simple is the idea that my mock objects create simple logs of what happens to them. These logs can then be used to confirm complex interactions took place.
This isn’t something I’ve seen much comment on, possibly because much of the noise being generated by people doing TDD is coming from those with a .Net or Java background and they have frameworks that create dynamic mocks which in turn have methods to set up a mock object to expect not receive a particular call, or to receive a call with particular arguments, etc. Jeremy Miller provides a nice example of this style of mocking in C# here.
Since I’m writing my mocks myself and since I’d rather be writing production code than mock frameworks I decided to avoid trying to have to provide a way to “set up” a mock to expect a certain sequence of calls before I run the code under test and instead turned the idea on its head and simply ask the mock to tell me what happened to it once the test has completed. It may seem to be a small distinction but it makes the mocks so much easier to code.
From one of my Practical Testing articles you’ll see that some of my mocks are as simple as this:
LogMessage(_T("OnTimer: ") + ToString(userData));
The key point is the call to
LogMessage(). This simply adds a line of text to the mock’s log. Each interaction with the mock object usually adds a line or two to the log.
Once the test is complete we can ask the mock what happened to it. If the log is likely to be small we can embed the expected outcome in the test itself with a call like this:
If the log is going to be more involved we can get the mock to check its log against a file on disk.
The later is useful for the more complex interactions that tend to occur when you’re testing objects higher up the food-chain. One of the nice things about the way my mocks check these logs is that if they don’t find the log, or if they do and the log doesn’t match what they expect then they dump out a log file of their current state. This can be used to bootstrap the test, you simply run the test without creating any expected interaction logs and when the test fails you have your logs. It can also be used to simplify the changes to the tests that are often needed when an object’s internals change and its interaction with the outside world is different to before. You don’t need to adjust your test, you just need to check that the new log that’s created when the test fails is correct and then replace the old log with the new one.
At first I didn’t like the idea of storing expected interaction logs on disk, for some reason it seemed wrong to me. Then I realised that these data files are just part of the test environment and are stored in CVS in the same way as everything else. Now that I’ve been doing it for some time I couldn’t think of doing it another way.
The advantages of having your mocks create interaction logs are as follows:
The code in the test itself is simplified, no complex set up of mock object expectations before the test starts. It’s easier to keeping the tests in step with internal code changes because the mocks will report what DID happen and if that’s correct then you simply update your expected interaction log and you’re green again. The logs can be very detailed, so your tests can be more precise