TDD’s Redeeming Quality
(Originally posted on 2012-10-31)
This is not the post I set out to write. Not by a long shot. I intended to write a scathing critique of TestDriven Development. A critique with so bulletproof and eloquent arguments that everyone who read it would be forced by the sheer weight of logic on my side to submit and accept that TDD was nothing but a passing fad. I thought I had it figured out and all those TDD enthusiasts were just being silly.
My first version was an ugly amalgam of sensational words which accomplished nothing except inciting my friends who read it to rage. After talking, at length, to them I realized I really didn’t want to add another ill considered rant to the internet. We already have enough developers shouting at each other over slight differences in development style. I don’t want add my voice to that cacophony. In the process I discovered that there was something fundamental I had been missing. I’d much rather talk about that in my virgin post.
Why TDD matters
Advocates of test-driven development claim that the best way of developing software involves writing tests first and only writing code to fulfil those tests, continually refactoring the shit out of the code. Some developers have taken issue with this strategy.
Simple designs are highly encouraged in TDD as the minimum amount of code needed is added in each iteration, with aggressive refactoring if things start to get too complicated.
Working with simple components with clear interactions really makes the mental burden of working with the code bearable. This simplicity comes at the price of expensive refactoring if the design of the api/interfaces needs to change. So to guarantee against that the developer must think long and hard about the design of his code and take time to get it right, just like his non-TDD practicing friends. As a result of these two factors TDD can take longer to produce complete features.
But that doesn’t really matter…
Test coverage and testability
I have seen few dispute when it comes to TDD test coverage tends be better. With good test coverage we get much fewer bugs escaping into the wild and greater confidence when enhancing and/or refactoring our code base. A lot of our mistakes will be caught by our unit-tests. Except maybe for some off by one errors, or similar edge cases, or weird exceptions, or rare interactions, or unexpected data combinations, or misunderstood requirements, or invalid assumptions about 3rd party apis, or the other one thousand and one mistakes that constitute the root cause of most bugs. Issues which you will certainlymiss if you focus only one those few happy paths through your code that TDD forces you to focus on.
Except it doesn’t really force you to focus only on those happy paths. Sure, if you expand your coverage to include those difficult cases refactoring will be more expensive, even radically so. However with a solid initial design this risk can be mitigated. Armed with a comprehensive test suite you really can have excellent confidence in your code and deliver some seriously high quality features. It is going to cost you.
But that doesn’t really matter…
What’s the problem then
From what I have gleaned of the often heated discussion abou TDD online the main problem seems to be overpromise. This would hardly be the first time that a new methodology comes along and promises to revolutionize the software industry. Experienced, solid software developers have a tendancy to take issue with starry eyed converts and inflamed evangelists telling them they’ve been doing wrong all their professional lives. Even if there might be a kernel of truth to those claims, at least in some cases. Very few people are in a position where better test coverage will not improve the quality of their code.
Too many people have reported great success for the methodology for it to be entirely useless. I think that TDD is a great way to encourage useful behaviors and practices in programmers hitherto unaware of the true benefits of those practices. To further enhance their testing practices I suggest that every developer interested in writing solid unit tests should be fluent in vocabulary of the istqb test certification program as well as the rapid testing methodology of James Bach and friends. To develop good tests developers should understand software testing as practiced by software testers.
Automated tests, unit or otherwise, are a fantastic thing. Giving serious thought to the design of your code, including testability, is an equally wondrous thing. Refactoring the bit-rot out of your code is an excellent practice. The claim that is implicit in the evangilism of TDD is that these fantastic practices somehow depend on you doing TDD. They don’t. You get a lot of the benefits by the practices it tangentally demands, not the ones central to it’s tenets.
The Heart Of TDD
When discussing TDD with my friends and coworkers, often heatedly, an interesting pattern has appeared. All of the arguments about expensive refactoring and the need for up-front design are never really challenged. Instead, what has always been the final refuge for those arguing for TDD is that it makes development fun again. It makes testing fun, and that is what really matters. There is no need to invoke questionable rationalizations. If TDD makes coding and especially testing fun that in and of itself makes it all worth it. Finding joy in your work is what counts. Turning a traditionally boring task, like testing, into something cerebral and exciting, that’s all the justification I need.
The way I see it, no matter what field you work in, the boring tasks tend to be the ones you skip or skimp on. Most developers I know seem to suffer from the delusion that testing is boring. It isn’t, not by a long shot. What we need is a way to show how testing can be fun. Maybe with TDD we’ll have developers testing the shit out of their code, no guns to heads, no superhuman discipline needed. Because it’s fun.