TDD

September 23, 2025
September 23, 2025

Green checkmarks don’t guarantee progress. It’s not about the badge or the acronym. It’s about whether you solved the problem that really matters.

As engineers in the world of modern software, it feels instinctive to automate everything. Right away. As much as you can. Because automation is Efficiency with a capital E… right?

Scripts, workers, jobs, evals, and — of course — tests. Tests! Test Driven Development. TDD. It’s got an acronym and everything. How would you know your thing is good if you don’t TDD? Are you even a real developer if you don’t TDD?

Fists

But when you don’t — because there are always reasons why you can’t — someone or something has to be to blame. Some inefficiency that kept you from doing “the right thing.” A blocker. A villain. And before you know it, you’re shaking your fist in the air, cursing tech debt, groaning about missing types, muttering about bad systems.

(I feel like I’m going to get a lot of annoyed eye rolls and disapproving head shakes for this post.)

So! I say this not to call out anyone else — but to call out my past self. Back then I blamed everything: the build system, the missing types, the lack of scaffolding. All the reasons why I couldn’t get that perfect, elusive 100% test coverage. Because that’s what the prolific folks on GitHub were doing. Green coverage badges meant good code, right? And if green is good, then maybe that means... I’m good too. Because if I were to be honest... The fists aren’t really for missing types. Not truly. They’re for the quiet ache of not being enough.

Sense

Don’t get me wrong. I’m not anti-TDD. I use it. I like it. When it makes sense. To me, it’s a way of working — not the Way.

So... when does it make sense?

I’ll start off by saying when it doesn't make sense.

If you have to fake 50%+ of your modules just to get a basic “hello world” test to pass? That test probably has very little value (if any) — even if your terminal is glowing “feels good” green.

This happens most often when:

  1. The environment simply doesn’t support what you’re testing.
  2. You don’t yet know what you’re actually building.

Environment

For the first one. Let’s say your UI depends heavily on two built-in browser functions: requestAnimationFrame and getBoundingClientRect. Maybe you’re trying to position something based on a user interaction.

You write a test. It fails. Perfect. That’s the point, right? Greenfield TDD energy. You roll up your sleeves, vibe-code a fix with the latest Claude Sonnet version-something suggestion and… FAIL. Huh. Maybe Claude isn’t as clairvoyant as you thought. Silly AI. So you strip it down, code it manually. FAIL. Again.

What the heck. You run it in the actual browser — it works fine. Smooth as butter. So what gives? You’ve now burned 2 hours of your life chasing down a test that refuses to pass, your terminal screaming red while Chrome quietly hums along like, “What’s the problem?”

Here’s the problem: Jest doesn’t use a real browser. It uses JSDOM (a fake browser). And JSDOM doesn’t support those two functions. Which means your test will never pass. It simply can’t.

So what do you do? What we all do. You fake the functions. Stub them out. Mock reality until the test believes the lie. And finally — bliss. That “feels good” green terminal accented with a soundtrack of the Windows XP startup sound. Success!

But step back. What did you actually test? Not your logic. Not the interaction. Just… that your component exists? That your fake code works? Congrats, you built a green checkmark out of cardboard. Cool.

Knowing

The second one. Probably the more common, quieter killer…

You don’t yet know what you’re building.

And how do you figure that out? You prototype. You experiment. You try things. Lots of things. Fast. You go through a process I call “scientifically throwing spaghetti at the wall.” You see what sticks. You kinda track how it stuck (that’s the “scientific” part).

Only once you’ve got a sense of direction — some clue of what you’re actually making — does TDD start to make sense. Until then, you’re just paving a road to nowhere.

Yes, you can use TDD as a way to discover the thing. But if the thing you discover looks nothing like what you thought, then... You just wrote a bunch of tests for the wrong world. Now you’ll need to toss them out or rewrite them to fit the real deal.

It’s like scaffolding and assembling an elaborate onion-cutting machine before you’ve even touched an onion. Because, ya know, “automation!” (exclaimed with a raised hands emoji).

Instead of building and rebuilding onion-machines, maybe just chop some onions first. Learn how the skins slip, how the tears sting, how the slices fall apart in your hands. Then — once you understand what actually needs to be chopped and how — go wild. Automate the heck out of it.

Cathedral

I’ve done this. Plenty. Unfortunately.

Years ago, I built a cathedral of test code — arches, buttresses, stained glass and all. Even scaffolding for the scaffolding. Code to test my code that tested the code.

And when it came time to pivot — because, surprise, I had built for the wrong thing — I couldn’t bear to tear it down. Do you know how much time I’d spent making sure every last piece was tested, stable, and working perfectly? (huge asterisk on “perfectly.”)

So I got defensive. Then defiant. This was my masterpiece! A shining monument to what “good engineering” looked like. And maybe, in some abstract sense, it was.

But not in the context of everything else. Not in the context of what the team needed. What the project needed. What the company needed. Where we were actually going.

So I let it crumble. Tore down walls. Salvaged what I could. Moved on.

And that’s when it clicked: “good” isn’t universal. It’s contextual.

Discernment

TDD isn’t good or bad. It’s a tool. Powerful when used well, pointless when forced. Like trying to brush your teeth with a hammer. Sure, technically possible. But why?

Here’s my (hot) take: as an Engineer, your job isn’t to write code. It’s definitely not to write tests.

Your job is to solve problems.

Code and tests are just the medium and methods in which you use to solve problems. The art is in knowing what’s worth building, and what’s worth testing.

And this isn’t really about TDD. It’s about problem-solving in general. About resisting the urge to do something because “that’s what good looks like.” About slowing down long enough to ask: does this tool, this framework, this method actually help me solve this problem?

Because “best practice” isn’t always best for you. Sometimes the “right” way burns hours you didn’t need to spend. Sometimes it adds complexity instead of clarity. Sometimes it’s just doing the motions of what you think good looks like, instead of figuring out what your situation actually needs.

Problem-solving isn’t about dogma. It’s about discernment. It’s being able to tell the difference between what looks impressive and what actually works. Between the process that makes you feel productive, and the process that moves you forward.

That’s the bigger game. And it applies to way more than code.

So calm your fists. Understand the problem. Work with others. Choose the tool that makes sense. And as you do, don’t forget to do good.

Because in the end, what matters isn’t the badge, the green checkmark, or the acronym. What matters is that you solved the problem — for real. For the people who really matter. In the context that actually matters.

Filed under:

Get posts via newsletter