In the course of my work, I get to spend time with people who have different points of view on software development compared to my own, and indeed compared to those of my colleagues at ThoughtWorks (with whom I tend to share notions about what constitutes pragmatic software development). Often these viewpoints are interesting and instructive, and sometimes provocative too.
In that spirit, I’ve been drawn into numerous conversations around test-first development. There has been some debate around my team around lazily – in the computing sense – testing rather than testing first, or indeed leaving some of the testing to QA to cover in the guise of functional tests.
This rationale is commonplace, that if we write fewer tests then our teams velocity will improve, and provided our overall code coverage is good, then the world is a happy and harmonious place. While you may choose to get embroiled in that debate (I have my opinions of course, and occasionally those opinions turn to outright apoplectic rage!) but that’s not quite what I’m interested in here.
What I’m interested in is that already we’ve focussed on testing, and I think that’s premature.
But wait, isn’t that heresy? How can modern software development which prioritises testing as an integral part, or even precursor to writing production code ever use the words “testing” and “premature” in the same sentence? Mr Fowler may have me whacked for even contemplating such thoughts.
Fortunately I think I’m safe from being whacked for now, because I still believe in writing software artifacts commonly called unit tests, but I really don’t think they are tests, at least not at first. You see, when we as developers are writing unit tests, what we’re actually writing is a (usually) partial behavioural contract against a component under test in a given context. Still we’re not writing tests per se, although the behavioural contracts we write are often littered with that four letter word.
It is the application of layers of such contracts to components in a set of contexts (with stubs and mocks where necessary) that resembles the component’s real use cases which drives out the design of that component. And it is (good) design that unit tests drive out, something which Dan North‘s BDD captures excellently.
This set of behavioural contracts finally becomes proper unit tests when the component is wired into the system under development and the developers move on. The testing aspect of unit tests finally emerges as tests continually exercise the fitness of the codebase as part of a continuous build and support refactoring as new functionality is added.
So we get a double benefit from test-first approaches, not only do they lead us to think deeply about design at design time, but their legacy is a test suite that documents and supports ongoing development of the system. This is precisely when the value of unit testing – design, components, and interaction – comes into its own.
Of course I can’t leave this topic without highlighting an important paradox: while agile teams may be accused of avoiding design and jumping straight to coding, the fact is that TDD and BDD force teams to consider component and interaction design very thoroughly before they write real code, leaving a clear and reproducible audit trail for any developers that follow. And that is something that even the most ivory towered of architects can’t admonish.