TDD "Isn't About Testing" Revisited
I’d written an article to my mailing list decrying the attitude of “TDD isn’t about testing, you idiot!” and a reader asked me a few things to clarify my position. In the process, they pointed me to an article that they considered a reasonable articulation of the issues involving the importance of writing tests first, then asked me what I think about it.
A Response To “TDD Is About Design, Not Testing”
I don’t love the author writing this: “But if you’re doing TDD so that you’ll gain better test coverage, you have not only misunderstood TDD, you’ve misunderstood testing as well.” I especially don’t love using the word “misunderstood”, because the author doesn’t live in the reader’s head and can only guess at what the reader understood. If I teach TDD to a programmer who uses it only to gain test coverage, then I might say that the programmer has “missed the point” (still quite presumptuous about there being a single point) or, less judgmentally, is “using a hammer to kill a fly”. If you merely want to increase test coverage, then you don’t need TDD1, and moreover, TDD doesn’t necessarily lead you to a well-tested system, even though it almost certainly leads you to a better-tested system than you had before.
That said, I understand that one might write plainly and provocatively with a highly-judgmental thesis in order to seize eyeballs. For example, we have to feed our families. I’ve done it.
The rest of the article describes what differentiates TDD from unit testing, which I do routinely in my training courses. I take time to do this because many programmers, particularly experienced ones, don’t see the potentially-significant extra beneficial effects of writing tests first, and particularly of critiquing the tests that they write first. Even so, I also don’t love the author writing this: “…we quickly see how TDD dissolves into nothing but an orientation towards unit tests.” From this I infer that the author sees TDD as “better” than unit testing. This reeks of “Best Practices” thinking, which I prefer to avoid. I would prefer to say that TDD goes farther than unit testing, providing other benefits, while (mostly) not losing the benefits of unit testing.2 I would also say that I sometimes pause while test-driving something to do some microtesting (almost the same as unit testing) when I think I need it. While TDD involves a larger idea than unit testing, that doesn’t mean that TDD is a greater idea than unit testing. Each has its purpose.
Now, I understand what might motivate someone to write this: don’t do unit testing and call it “TDD”, because that’s confusing or wrong, whatever. I worry about creating confusion, too, in an industry with already more-than-enough confusion in it. I take pains to say, “if you want to write the tests after the code, then please don’t call that ‘TDD’, because TDD assumes test-first programming”. Even so, if you are willing to change your design after you find out how difficult it is to write the tests, then perhaps I can call that TDD, albeit a painfully slow version of it! To optimize, stop writing the production code before you write the tests. I prefer this approach to the topic than “that’s not TDD”. When you say “That’s not TDD!” the other party can too easily ask “Who cares?!” and be right to ask. We don’t just do TDD for its own sake.
I would even edit this line of Bob Martin’s: “The act of writing a unit test is more an act of design than of verification.” I prefer something like this:
When writing a microtest, we don’t just specify the behavior we want, but also often make design decisions. Over time, the programmer might shift from focusing on the former to focusing on the latter to considering them both very carefully. Herein lies some of the great power of TDD.
I prefer to avoid “you’re doing it wrong” and instead to invite the reader to consider “there’s more stuff here when you’re ready”.
Thinking At a Sustainable Pace
By coincidence, just after I edited this article, I saw on Twitter that Uberto Barbini had published a short article in which he claims that “TDD… is about thinking at [a] sustainable pace.” I had never formulated the idea this way before, but I like it, and I intend to share it widely. I still don’t like “…is about…”, but I understand the impulse. I really like Uberto’s conclusion that, “If Waterfall is like expedition-style and Cowboy coding is like free climbing, TDD is like alpinism. Safe but still quick enough.” I would go one step farther: TDD feels both like alpinism and alpinism training. In other words, the TDD practitioner not only designs safely-but-quickly-enough, but also learns—and deeply understands—the principles that help them design safely-but-quickly-enough.
Related Reading
Kelly Sutton, “Design Pressure”. “Design Pressure is the little voice in the back of your head made manifest by a crappy test with too much setup.” I call this “positive pressure” and routinely refer to it in the context of the integrated tests scam. Microtests exert more positive pressure on my designs, which I favor.
Andrew Binstock, “TDD Is About Design, Not Testing”. Another opinion on the relationship among TDD, design, and testing. You can find several such articles on the web with varying positions.
Uberto Barbini, “What I’m talking about when I talk about TDD”. I feel good knowing that Uberto and I have come to such similar conclusions after taking mostly independent paths from a similar starting point. Perhaps I can cite this as evidence that I’m not crazy.
If you (only) want to increase test coverage, then it suffices to write tests. In trying to write tests, you might wish to redesign parts of the system to make those tests easier to write and understand. You don’t even need to write the tests first! All that said, I like test-first programming as a stepping-stone practice that improves test coverage and brings design problems to the programmer’s attention, but if you have no intention ever of challenging your ideas about how you design software, then TDD might frustrate you more than it helps you.↩︎
When practising TDD, I sometimes skip tests if I sense that they won’t drive me to making new design decisions. I do this as a shortcut and to avoid writing tests first becoming a dogmatic ritual. I might, after completing a task, add tests “just for testing” and “to protect against future refactorings”, but I sometimes need a pair partner to remind me to do this and I sometimes still don’t do it. As you might expect, I trust myself due to having practised for nearly 20 years; I didn’t trust myself like this for the first five years that I practised test-first programming and later TDD.↩︎