Given a software project whose outcome and schedule truly matter, all other things being equal, I'd choose…
- A capable team of TDD practitioners over another capable team
- To have the team practice TDD
Of course, there are always a great many other things, and they be rarely equal. Let's scale it down a bit.
Given the responsibility to deliver working, valuable software to humans, I'd choose to drive my development with tests. I believe I fulfill my responsibility faster and better when I do. This turns out to be one of my strongest and most deeply held beliefs of any kind, right up there with the intrinsic value of human beings and an overarching respect for individual autonomy.
Yeah, that sounds a little odd to me too. Yet my professional experiences have repeatedly borne out the validity and importance of all three.
Sticking with TDD here, I've read and/or conjectured lots of plausible mechanisms by which it could be speeding me up, I've observed a bunch of them directly, and anecdotally I'm usually pleasantly surprised how smoothly things go, compared to how I imagine they might have gone instead.
Software development isn't a controlled experiment. It's possible I've been continually and subtly lying to myself under the sway of a whole bunch of cognitive biases and thus I've been egregiously misinterpreting my personal anecdata.
Since “I do my job better when I practice TDD” is merely a strong belief, not a result that I've proven, I owe it to myself and the people I work with to question it from time to time. Inspired by some recent professional experiences, now is one of those times. Where are the boundaries of my belief? If I'm trying to be effective and efficient, when wouldn't I practice TDD?
What I mean by TDD
TDD, to me, means holding a collaborative conversation with my code. I suggest what I think I want it to do next, ask it to try, listen to what it tells me, adjust my thinking and wanting, and repeat. To listen better and adjust faster, write smaller tests (in units of code, if inside-out; in slices of functionality, if outside-in).
If that metaphor doesn't quite work for you, try this one: TDD means pair programming with myself. Before typing anything, I communicate my intent aloud. Maybe my pair has a better idea, or a question to consider, or a drawing board we should be going back to. By working in a way that externalizes my thinking, I can be my own pair.
Degenerate cases
I wouldn't want TDD if…
- It doesn't need to work
- It needs to work, but it doesn't matter when
- It's so manifestly simple that it necessarily works
- It won't ever need to change
- It's so manifestly simple that it'll be obvious how to change and necessarily still manifestly simple afterward
- It's a throwaway experiment to learn something
- I'm being held at gunpoint by a crazed stakeholder who'll shoot me if I write so much as a single test first (or try to have a conversation about it)
None of these cases is particularly interesting.
False negatives
You might think I wouldn't want TDD if…
- It's legacy code
- It's otherwise hard to write tests for
- Nobody else on the team bothers
- I'm learning a new programming language
- Unarmed stakeholders express opposition to it
You'd nearly always be wrong.
Learnings go here
I practice TDD for its many benefits. What if I could achieve those benefits another way?
As a useful oversimplification, the biggest external benefit of TDD might be that I get my work done faster and better, and the biggest internal benefit might be that I feel safer doing my work. (It seems plausible that there's a causal relationship.) I've been working in a dynamic language famous for its runtime surprises, deriving a feeling of safety from my tests. Might there be a language where the compiler alone could give me that same feeling?
I can practice TDD because I've been targeting suitably unconstrained environments. Might there be a target whose constraints preclude TDD?
I don't know for sure when I wouldn't want TDD. But I've got some ideas, and watch out: I'm going to test them.