DEV Community

Cover image for Why I Write Tests Before I Write Code Now
Misar
Misar

Posted on Originally published at misar.blog

Why I Write Tests Before I Write Code Now

Why I Write Tests Before I Write Code Now

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.

Quick Answer

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.

The week that converted me

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.

A developer staring at a screen full of failing test output
Photo by cottonbro studio on Pexels

The test is really a design document

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 });
Enter fullscreen mode Exit fullscreen mode

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.

A test you can't write is a design that's wrong

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.

The feedback loop is the real productivity hack

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.

Clean rows of green checkmarks and code on a monitor
Photo by Jakub Zerdzicki on Pexels

How I actually do it without it being miserable

I'm not a purist. I don't test-first everything. Here's my actual, pragmatic workflow.

  1. I test-first the logic, not the glue. Pricing rules, parsing, business logic, anything with branches and edge cases — test first, always. Wiring and trivial pass-throughs, I don't bother.
  2. I write one failing test, make it pass, repeat. Small steps. One red, one green, move on. I don't write thirty tests up front; I let them grow with the code.
  3. I let the tests be ugly at first. The first test just has to pin down the behavior. I clean it up once the design settles. Done beats elegant here too.
  4. I treat the edge cases as a checklist. Writing tests first makes me enumerate the weird cases before they bite me — the empty input, the boundary, the zero, the negative. That enumeration is half the value.
  5. I lean on tooling. Good test runners, watch mode, and AI assistants for generating boilerplate test cases make the friction low enough that I actually do it. The lower the friction, the more I test.

The whole thing adds maybe ten minutes at the start of a feature and saves me the entire pricing-calculator week at the end.

FAQ

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.

The bottom line

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?


Related Articles in the Misar Ecosystem

Explore More Across Our Platforms

Recommended Products and Tools

Key Takeaways

  • Write tests before code to define what 'done' means and expose bad design before committing to it, resulting in a faster and less stressful development process.
  • A test you can't write is a sign of a badly designed system, and rewriting the design to make the test easy is a key to cleaner code.
  • The test-first workflow provides a tight feedback loop that's faster than manual checking, allowing for instant regression detection and fearless code changes.
  • Prioritize testing logic and business rules over trivial pass-throughs, and let tests grow with the code in small steps to maintain a pragmatic workflow.
  • Use tooling like test runners and watch mode to reduce friction and make testing easier, aiming for a balance between testing and development speed.

Originally published at https://www.misar.blog/@misar/articles/why-i-write-tests-before-i-write-code-now

Top comments (0)