I was at lunch today with a few other ThoughtWorks folk. At the end of the meal Chris, George, and I were talking when George mentioned he’d been pondering some stuff with Mocking and Stubbing. George was questioning a suggestion that Mocks should not be used as a way of testing the edges of a system.
I replied along the same lines as my Extract Client Interface post (perhaps a little less eloquently) where I mentioned that writing code with Mocks encourages you to think about roles and interactions with collaborating objects things first, rather than getting buried under the weight of implementing everything in the world. And, perhaps more importantly, if you depend on something else, you’ve discovered an interface for what the client really needs.
If you do this for the edge of your system, you’ll end up discovering an interface that will get implemented with a facade or adapter that lets your code talk to the external system. But how do you test that? Should you never write an interaction-based test for that?
On our way out of the restaurant we carried on talking about these kinds of rules in software development - that you should never mock things you don’t own, conditionals are bad, regions are evil. Although there’s value in the statements, what’s more important is that people think about them. It’s like the agile manifesto, nobody says never write documentation, just that working software is valued more.
And, although I neglected to mention it at the time, it occurs to me that a recent experience on my current team is actually quite applicable to what we were talking about.
At present some of the code we’re working on integrates with a C++ library provided by another department. Fortunately, it exposes a couple of functions that let us dump it’s internal objects into an XML document. So, to test that we interact with the library in the correct way (across multiple function calls - state is maintained in a ‘cache’ behind everything) we assert on bits of the XML.
We’re essentially asserting on the internal state of an external library to ensure the correct interaction, and, when we updated to a new build of the library our build broke rather expectedly. We had an hour or two to fix up the tests when the schema of the XML changed. We’d depended on an external interface that we didn’t own and paid the price for testing our integration this way when the external system changed.
But, this same approach to testing (i.e. testing interaction rather than state) has also let us (to a degree) focus on implementing what’s important, and drive out what was previously considered a complex, black magic type development effort, into something more understandable and controllable. It’s by no means perfect, but as a small step on the way to development nirvana, it’ll do.
We might have done something perhaps a little evil, but the techniques and tools we use to discover better ways of writing code have helped us in a situation the rule would otherwise have prevented us. Sure we’ve had problems because we’ve been depending on things we don’t own, but, that’s a small price for the benefit we’ve gained - a clear step as we divide and conquer our way through.
Rules are important for providing insight into good ways of working. But, it’s always important to think and act intelligently. Sometimes things people say are a little over the top, but, they put questions in your head to challenge assumptions. It’s no good just looking for that next shiney pattern in a new book, or putting index cards on a wall. Those things may help, but they’re not an end, they’re a means. The key is intelligence and people (as has already been mentioned) continually adapting, learning, and improving.
There’s no such thing as a rule to rule them all. Well, except maybe that rule :)