TDD is for snobs

By Daniel Samson · 2023-05-26

I write tests. I just don't write them first, and I'm done pretending the ritual is what saves us. Test-Driven Development has been sold as a discipline that drives good design and catches bugs. In practice it mostly catches the bugs you already imagined.

The promise versus the reality

The promise: write a failing test, write the code to pass it, refactor fearlessly. The reality: you write a test for the implementation you've already pictured in your head, then the moment you refactor anything meaningful, half your tests go red — not because the behaviour broke, but because the shape changed. You've pinned the code in place and called it confidence.

Unit tests catch the bugs you thought of

A red-green unit test can only fail in a way you anticipated. But real production incidents are almost never the thing you anticipated. They're race conditions, bad data from upstream, a third party timing out, a config difference between staging and prod, an integer that overflowed in a currency nobody tested. Your beautifully mocked unit test sailed straight past all of it.

Test behaviour, not units

The tests that actually earn their keep run against real-ish dependencies: integration tests against a real database, end-to-end tests against a running system. They're slower to write and slower to run, and they catch the things that actually happen in production. That's the trade I want.

Where TDD genuinely shines

I'm not a barbarian. TDD is great for pure functions with clear inputs and outputs, for gnarly algorithmic code, and for reproducing a bug before you fix it. Narrow, deterministic problems are where the red-green loop pays off. Be honest about that scope instead of applying it as a moral framework to the entire codebase.

So: write tests. Write the ones that catch real failures. Stop performing TDD for the gallery and calling everyone who ships differently a cowboy.