Discover more from Software Design: Tidy First?
I’ve been shocked by the TDD backlash. “It’s bullshit.” “It’s impossible.” “You can’t make me.” Somebody must have hurt these folks badly. Now, it turns out Soundararajan was asking their question in good faith (see the end for a comment not made in good faith). We agreed that “use it when it makes sense & not when it doesn’t” was a reasonable strategy.
Let’s step back & ask, “Under what conditions can TDD work?” Then, if those conditions don’t hold, we can all agree you need to find another workflow to meet your needs & the needs of your customers.
We’ll start with outcomes. What outcomes is TDD intended to produce? If you don’t value these outcomes, then don’t bother with TDD. You’ll be spending time & money to buy fruit you don’t want to eat.
Ease. I’m anxious. When I program I’m especially anxious. TDD is intended to soothe my anxiety. When I know I’ve covered all the cases that I imagine could break, I relax.
Reliability. I write code wrong (sense of test is a fertile source of bugs for me). TDD is intended to reduce the number of errors I pass downstream.
Sustainability. I continue changing my code, both the behavior & the structure. TDD is intended to support ease-ful change indefinitely. (Some folks report that the way they write tests reduces the chance for further change—Tidy First? is my take on how to avoid that problem.)
Responsibility. I want to count on my colleagues & for my colleagues to be able to count on me. TDD is intended to communicate the thought process behind the code.
If the above outcomes don’t sound good to you, then don’t bother with TDD. If TDD sucks but you still want the above outcomes, then you’ll have to find another way to achieve them.
Let’s say we agree on our valued outcomes. We might still not be able to use a TDD workflow to achieve them. Here is my list:
Predict inputs/outputs. Can you work out the answer before coding? If you’re writing a simulation & you don’t know what the answer should be, you can’t TDD. You may be able to TDD the predictable building blocks of your simulation, however.
Predict/discover cases. Can you guess some of the important scenarios & then discover the rest as your implement? If the test cases that affect overall system reliability come as a complete surprise, then you can’t TDD. You may be able to TDD regression tests, however.
Micro-tests yield macro-results. Can you run the tests & have confidence about the whole system? TDD composes all these little bits of reified confidence to give you a sense of the health of the whole system. If that composition doesn’t work, if the emergent properties dominate, then you shouldn’t bother with TDD.
We can keep tests in some part of the test desiderata acceptable zone. If I write non-determinstic tests, if I write slow tests, if I write tests that are coupled to the structure of the system, if it takes me forever to write the test setup, then TDD will just be annoying. I think these are all software design issues, not testing issues.
And so to Soundararanjan’s question: given the constraints as presented, then only apply TDD when the costs are reasonable. I’ve just made 5 minute changes to code when:
Testing that change would have required hours &
I had other ways to double-check my change.
I also look for ways to erase those constraints. What would the system’s structure need to be for it to be easy to test? I don’t necessarily make those structure changes now, but I want that option to be available.
We agree, Soundararajan.
And Frank, we agree that you should write tests & code in whatever order you want. It seems a bold claim that the order doesn’t matter. Code however you want & then deal with the consequences. You & your customers, deal with the consequences. Bless your heart.