← All essays

Why I stopped writing tests first

Tests-first taught me to think about interfaces before implementations. Then AI changed which phase is actually expensive — and I moved the ritual upstream.

March 12, 2026

At my day job, the code I write feeds into actual loan decisions. There's a regulatory paper trail. If a valuation service returns the wrong number, somebody's auto loan goes sideways. Testing isn't optional there — it never was.

When I started shipping indie products on the side, I assumed the same rules applied. They didn't. Working that out took longer than it should have.

What TDD was actually doing

The real value of tests-first was never the tests themselves. It was the forced pause — the ten minutes where you stare at the interface before committing to an implementation. TDD was a ritual for thinking about shape before detail.

That part is still essential. I haven't stopped thinking in terms of inputs, outputs, invariants, and edge cases.

What I've stopped doing is encoding that thinking in describe("UserService") blocks before the code exists.

What changed

A few years ago, the expensive phase of a feature was writing the correct implementation. Tests-first constrained the expensive thing.

In 2026, implementation is cheap. A well-specced component gets written in minutes by a Claude Code session pointed at a sharp spec. The expensive phase has moved upstream — to the spec.

So the spec comes first. Edge cases, success criteria, invariants — all named there. Then implementation. Then, if the code matters enough, tests.

"If it matters" — the heuristic

I still write tests. I just don't write them first, and I don't write them for everything.

I write tests for:

  • Anything that touches money, auth, or PII
  • Parsing, encoding, or anything with a spec document behind it
  • Pure functions with non-obvious edge cases
  • Code that other services depend on contractually

I don't write tests for:

  • One-off migrations
  • UI I can see working in a browser in ten seconds
  • Anything where the test would mostly assert that the library I'm using works

This is the same heuristic a senior engineer would have used in 2018. What changed isn't the heuristic — it's that the spec now does the work tests used to.

What I was really afraid of

The real fear with skipping tests-first was that I'd lose the interface-first thinking. That I'd start coding without knowing what I was building.

Writing the spec first solves that. Better than tests, actually — specs express things tests can't. "This should feel fast." "The error should never mention the word 'database'." "If the model gets it wrong, fail loud, not silent."

The ritual is the same. The artifact is different.