
For most of my career, I thought writing tests first was a religion practiced by people who didn't have real deadlines.
Tests came after. If at all. After the feature worked, after the demo, after I'd moved on — which is to say, never, often.
Then a single miserable week broke me, and I've written tests first ever since. Not because of dogma. Because it's genuinely faster and less stressful, and I can prove it to you. Here's the conversion story.
I write tests before code because the test is a design tool, not just a safety net. Writing the test first forces you to define what "done" means before you've built anything, exposes a bad design before you've committed to it, and gives you a tight feedback loop that's faster than manual checking. It feels slower for ten minutes and is faster for the whole rest of the feature.
I was building a pricing calculator. Discounts, tiers, edge cases, the usual quietly-horrible domain where every rule has three exceptions.
I did it my old way: build the thing, click around to check it, ship. It worked. Demo went fine.
Then the bug reports started. A discount stacking wrong here. A rounding error there. An edge case at exactly the tier boundary. Each fix I made by hand broke something else I couldn't see, because the only way I had to check the whole thing was to manually re-click every scenario, and I never did, because there were forty of them.
I spent a week in that loop. Fix, manually check the two things I remembered, ship, break a third thing I forgot. It was death by a thousand regressions.
Somewhere in that week I finally wrote the tests — after — and watched them light up red in places I'd been silently shipping bugs for days. If those tests had existed before I wrote a line, that entire week wouldn't have happened. It's the exact regret I unpack in the testing habit I wish I'd started earlier.
Photo by Ilya Pavlov on Unsplash
Here's the reframe that changed everything for me. I used to think a test checks code. It does, but that's the boring half.
The important half: writing the test first forces you to decide what the code should do before you build it. You have to name the function, decide its inputs, decide its outputs, decide what counts as correct — all before you've written a single line of the messy implementation.
That's design. And doing it first, in the test, is way cheaper than discovering it halfway through the implementation when you've already committed to a shape.
// writing this FIRST forced three decisions:
// 1. what's it called 2. what goes in 3. what comes out
expect(priceFor({ tier: "pro", seats: 5, coupon: "SAVE10" }))
.toEqual({ subtotal: 250, discount: 25, total: 225 });
I wrote that line before the function existed. And in writing it, I'd already answered the questions that, in my old workflow, I would have stumbled into messily three hours later. The test forced clarity up front, when clarity is cheap.
This is the benefit nobody told me about, and it's the best one.
Sometimes I sit down to write the test first and I can't. The setup is impossibly complicated. I need to mock six things. I can't figure out how to isolate the one behavior I want to check.
Old me would have powered through and built the tangled thing anyway. New me has learned that the pain of writing the test is the design feedback.
If something is hard to test, it's almost always badly designed — too coupled, doing too much, reaching into too many things. The test isn't being difficult. The test is telling you the truth about your architecture, before you've built it.
A hard-to-test design is a hard-to-change design. The test is just the first one to complain.
So now, when a test fights me, I don't fight the test. I change the design until the test gets easy — and the resulting code is always cleaner. The test-first workflow turns "this is hard to test" from a complaint into a free design review. Reaching for that kind of feedback loop instead of brute force is a big slice of the brutal truth about becoming a senior developer. Martin Fowler has written extensively on exactly this idea — that testability is a proxy for good design.
Let me be concrete about why this is faster, because "it's faster" sounds like a slogan.
In my old way, my feedback loop for "did this work?" was: save, switch to the app, click through the scenario, eyeball the result, switch back. Call it 30 seconds, and that's if I remembered every scenario, which I didn't.
With tests first, my loop is: save, watch the tests run, see green or red in a second. Automatically. For all the scenarios, including the forty I'd never manually re-click.
| The old loop | The test-first loop |
|---|---|
| Save, switch apps, click around | Save, watch tests run |
| Check the cases I remember | Check every case, every time |
| ~30 seconds, error-prone | ~1 second, exhaustive |
| Regressions found by users | Regressions found instantly |
| Dread before every change | Confidence before every change |
That last row is the one that matters most. When I have tests, I change things fearlessly, because the moment I break something the test tells me. Without them, every change is a small act of courage, and courage is exhausting to spend all day.
Photo by John Schnobrich on Unsplash
I'm not a purist. I don't test-first everything. Here's my actual, pragmatic workflow.
The whole thing adds maybe ten minutes at the start of a feature and saves me the entire pricing-calculator week at the end.
Q: Doesn't writing tests first slow you down? For the first ten minutes, yes. Across the whole feature, no — it's faster, because you stop losing hours to manual checking and regressions. My old "fast" way had a hidden week-long tail of bug-fixing that the test-first way deletes.
Q: What if requirements change and I have to rewrite the tests? Then your tests change with the design, which is normal and cheap. If a small requirement change forces you to rewrite every test, that's a signal your tests are coupled to implementation details instead of behavior — fix that, not the workflow.
Q: Should I test-first when I'm just prototyping or exploring? No. When you genuinely don't know what you're building yet, exploring freely is the right move. Test-first shines once you know the behavior you want. Prototype to discover the what, then test-first to build the real thing.
Q: How many tests is enough? Enough to change the code without fear, no more. Chase the branches and edge cases, skip the trivial getters. Test coverage as a number is a trap; "can I refactor this confidently?" is the real metric.
Q: My codebase is a mess with no tests. Where do I even start? Don't try to test everything. The next time you touch a gnarly piece, write a test for the behavior you're about to change first. Grow the safety net one feature at a time, right where you're already working.
I resisted test-first for years because I thought it was about discipline and dogma. It isn't. It's about selfishness — it makes my week less miserable.
Writing the test first isn't slower. It just moves the slow part to the beginning, where it's cheap, instead of the end, where it's a week of regressions.
The test stopped being a chore I owed and became the tool that tells me what to build, warns me when my design is bad, and lets me change code without fear. That's not religion. That's just leverage.
If you've been test-skeptical, try writing just one test before your next gnarly function and see how it changes the design — and the senior-developer pillar has more habits like this that compound over a career.
So next time you're about to dive into a gnarly piece of logic — try writing down what "correct" looks like first. What would your code look like if it had to be easy to test?
No following, no network, no luck. Just an unglamorous system I ran for eighteen months. Here's exactly what I did.

I went from 200 to 11,000 subscribers without hiring anyone. AI didn't write my newsletter — it did everything around it.

I chased big, audacious goals for years and burned out every time. Then I built my whole life around wins so small they felt like cheating.

Comments
Sign in to join the conversation
No comments yet. Be the first to share your thoughts!