When Test Driven Development Shines

Thanks to Ainent for posting their thoughts on Test Driven Development (TDD).

Unit Testing & TDD, A Love/Hate Relationship

Here are mine.

Too Simple

TDD is often explained as a flow chart to follow, along an example of the “don’t code it until you test it” rule; in Ainent’s post the example given is that if “2 + 2” is a test case then the answer should be hard-coded as 4; it’s the next test case that will force you to “implement” addition.


/----------------------\    /---------------------\    /-----------------\
| add minimal red test | -> | implement minimally | -> | test goes green |
\----------------------/    \---------------------/    \-----------------/

Examples like this are technically correct—but I think they mislead a little, they are too simple and so they give the wrong emphasis. It’s like trying to explain how to run by pointing out what you should be doing with your toes.

The Payoff

The magic of TDD is not that you get high test coverage in terms of % of lines of code executed; that’s not particularly hard to get if you put in nontrivial effort. No, the magic is that you get tests that you are pretty sure will fail if a feature doesn’t work; tests that are equivalent to your feature set.

The “red test, implementation, green test” cycle is a mechanical way to achieve this with a reasonable degree of certainty. The “red” part demonstrates that you are testing something that doesn’t exist yet; so that when you hit “green” it’s reasonably likely to be because the implementation work you just did matches what the test is asking for.

But, it’s not for certain; there is no actual guarantee that if you proceed in “minimal” steps then you will end up with correct test coverage. You are supposed to add minimal test cases until you are forced to write a “real” implementation instead of hardcoding responses to specific test cases; but this feels a little contrived. At this point I like to bail out on the mechanical TDD process and think about what’s happening.

The Core

I would argue that the core of TDD is considering your test set equivalent to your feature set, in a very specific sense: if a feature is not tested then it is considered accidental and so undesirable. Test cases that do not test features are similarly undesirable.

Exactly how you get there is not as important. The red to green test cycle is a critical tool; starting with simple implementations then building them up as you add new tests is a critical tool; but in the end it’s the state of your tests in relation to your code that matters.

And so the way I prefer to use TDD, I allow myself to think about what I’m doing, what test coverage is correct for it, and just go straight to that. Getting a “red” test cycle along the way helps me by reducing what I have to think about and check—it’s not an arbitrary box to tick.

Shiny TDD

This has all been a roundabout way of getting to a point where I can describe my most rewarding use of TDD: in a Dart library that I wrote as a side project at work and have been maintaining for about seven years.

It’s widely used, so a lot of people depend on it; it’s complex, because it involves code analysis and generation; and because it’s a side project I have very little bandwidth to maintain it. Sometimes I get a report of an issue then I have to dive into the code, fix it and release within a day; otherwise I might go for months without touching it at all.

I’ve been strictly using TDD all along because I knew I’d always find it a stretch to maintain properly.

And it’s worked well. It hasn’t made the code bug free; far from it; what it has done is let me dive into the code and make small fixes or add small features when I want to without worrying about breaking anything. If it’s tested, it’s supported; if not, it’s not. So I can move quickly whenever something comes up, commit the change with new test coverage, release—and get back to my main job.

I use TDD fairly often on personal projects, for much the same reason: there’s a good chance I won’t touch the code for a while then will want to jump back in; using TDD means I can add a small feature any time I like without having to re-grok the whole thing to avoid breaking something.

Conclusion

I don’t usually follow strict TDD, but I think that when it’s the right tool for the job it’s a very good one; and for that reason I recommend having it in your toolbox.

Feedback 📮

👍 Thanks!

👎 Not for me.

🤷 No opinion.

Comments.

So far today, 2024-11-25, feedback has been received 2 times. Of these, 0 were likely from bots, and 2 might have been from real people. Thank you, maybe-real people!

   ———
 /     \   i a
| C   a \ D   n |
   irc   \     /
           ———