← Back to list

Keep It Honest: The Evolving Role of TDD in the New Software World

The landscape moved under us, and nobody sent a memo.

Shalev Shushy · 2026-07-29 15:40 · 0 claps · 11.7 min read
#tdd #agentic-ai #red-green-refactor #ai #adaptation
Open on Medium ↗
Wiki topics: AGT · AI Agents AI · AI · General

Keep It Honest: The Evolving Role of TDD in the New Software World

The landscape moved under us, and nobody sent a memo.

A year ago I was a developer who used an AI assistant. Today I’m a developer who mostly writes rules, specs and reviews while an agent writes the implementation. I still code. I just don’t type most of what ships.

And that should worry anyone who cares about TDD, because coverage was always the after-effect, not the point. Small steps, because human working memory is small. Watch it fail first, because humans fool themselves about what they just proved. Minimum code to green, because humans gold-plate. Refactor separately, because humans can’t hold two intentions at once.

Every single justification for red-green-refactor is a compensation for a human limit.

So what is the cycle worth when the thing writing the code doesn’t have those limits? I’ve spent months trying to answer that. Here’s how it went.

The rules kept getting ignored

My first instinct was the obvious one: write everything down.

I put my entire coding philosophy into a CLAUDE.md that loads into every session. Named predicates instead of raw comparisons. Builders for test data. Test drivers. Strict red-green-refactor with a commit at every green. I even added intellectual references from major software figures so that the agent and I would speak the same language.

It didn’t work. Not partially - it barely worked at all.

The file grew to around two thousand lines, and the more I wrote, the less any individual rule mattered. A rule sitting in bold, in a section loaded into every single session, was being ignored. I moved things into skills instead and hit the opposite failure: skills only help when they get invoked.

So the enforcement started climbing. Prose became lint rule. Lint rule became a hook that blocks the edit outright. I have a commit gate that runs typecheck, lint and the full test suite before a commit is allowed to exist, and a gate that refuses to let a stateful component exist without a test file next to it.

I didn’t set out to build a cage. It accumulated, one ignored rule at a time.

Two things I learned building it. First, rules get dumber as they get teeth: a linter can’t check whether you named a boolean well, so the enforceable version just bans the > and < characters in component files. Second, prose loses in ways you won't notice. A rule banning positional parameters sat in that always-loaded file while a function with seven positional arguments lived in my shipped code for a month. A linter trial found it. Reading it never did.

Somewhere in that file there’s a sentence in quotation marks that I clearly said out loud, with some heat, after the agent spiraled through three workarounds for a simple problem. It got transcribed into law. That’s what this whole system is: my judgment, fossilized, one frustration at a time.

The file only got better when it got smaller. I cut it down and the agent got noticeably smarter - the scarce resource was never the token bill, it’s attention. Token efficiency, done properly, is context discipline: knowing what deserves to be in the window at all. We spent two years learning to prompt. What comes next is learning what to leave out, and almost nobody is practicing that yet. Start early.

A machine that tests itself

What I ended up with was, on paper, beautiful. The agent writes the failing test, the implementation, refactors, commits at every green, and can’t commit at all if anything is red. A review stage then reads the diff, finds defects, and writes them into the plan file as the spec for the next story. The output of one review becomes the input of the next cycle - a self-feeding TDD machine I steer with a few words at the start of a session.

Kent Beck calls TDD a superpower for working with agents, and his reasoning is the one I was betting on: agents introduce regressions, and tests are how you stop them. His model of an agent is a genie - it grants your wish, but rarely the way you meant it, and it will happily delete your tests to make them pass.

So I caged that genie so it couldn’t touch the tests. What I didn’t anticipate is that the genie doesn’t fight the tests. It fights the cycle.

Left alone, it writes tests shaped by the code that already exists - the exact opposite of TDD. My commit hook that demands a test file next to a stateful component catches the shape of the violation and none of its substance. It knows a test file is there. It has no idea whether a spec came first.

And I can’t prove one ever did. The workflow commits at green, so the test and its implementation land in the same commit, every time. Zero commits in my entire history land a failing test on its own. The cycle I’m devoted to leaves no fingerprint. If a discipline produces no artifact and no measurable difference, did it happen?

So when the loop slips (and it slips constantly), what saves me is the review stage. Expensive, slow, completely backwards. The tests were supposed to be the net that made heavy review unnecessary. Instead review became the only net, and the tests became something I pay for twice.

So I audited it

I felt productive. That’s worth nothing. METR ran a randomized controlled trial and found experienced developers were 19% slower using AI tools while believing they were 20% faster. Feelings and clocks pointed in opposite directions.

So when Fable launched, I asked the agent to use it to audit its own repo. Full forensics on a roommate matching app I’ve been building: git history, every bug in the changelog, how each one was actually found. I told it explicitly that a flattering answer was useless to me.

Here is the number that matters.

Bugs caught by an existing test going red during development: zero.

Not few. Zero. Every defect in the record was found one of two ways: a review agent reading the diff cold, or me using the app and noticing something was off. The tests got written afterward, to pin the fix. That test suite (48% of everything hand-written in that repo) is a regression net that has never once been a tripwire. This is not TDD at all.

The objection to my own audit

The obvious conclusion is that TDD with an agent is theater. That was my conclusion for a while. Then I ran into an idea that rearranged it.

The value of TDD was never the test artifacts. It’s the sequence of decisions that produced them - the vocabulary, the domain concepts, the public interface, and consequently a design. The tests are just residue left behind by that thinking.

Sit with what that does to my audit. I audited the residue and concluded the thinking was worthless.

Goodhart’s Law: when a measure becomes a target, it ceases to be a good measure. I’ve quoted that line at people about coverage percentages for years. It also applies to going looking for a number that proves your discipline works. Had I found one, I’d have started optimizing it, and destroyed the thing I was measuring.

I’ll take the uncomfortable half too. If the value lives in the thinking, then delegating the implementation means I’ve handed over part of the thinking, not just the typing. I own the grammar, the patterns, the bans, the structure. The agent writes the sentences. I don’t have a clean answer to whether that’s still TDD, and I’m not going to pretend I do.

So the null result settles nothing on its own. What settles it is a different question.

Three depths of not trusting the machine

“Can I leave the agent alone?” isn’t one question. It’s the same question at three ranges, and each one is harder to see than the last. At the first, it can’t see what I see. At the second, it can’t see that I’m wrong. At the third, it can’t see that it’s me.

Depth one: it cannot see or feel

I believe clever code is just means to a cause, and that the product is always the main goal. Frontend and UX are what truly determine the end quality of a project, and they happen to be major weaknesses of agents writing code - because they require a human touch.

That territory has an entire class of defect with no possible sensor. Not a test, not a linter, not an AI reviewer.

An animation that lingers 150 milliseconds too long is the purest example. expect(delay).toBe(300) passes. The delay still feels wrong. The only instrument in existence that detects it is a human being annoyed. Same for a tap target that's technically 44 pixels and still feels cramped.

The state of the art for validating agent UI work in 2026 is having the agent capture screenshots and videos so a human can check them - a polite way of saying the agent can generate a component but cannot tell you if it feels good. Birgitta Böckeler puts the mechanism better than I can: an agent has no aesthetic disgust at a three hundred line function and no intuition that “we don’t do it that way here.”

My own record agrees, embarrassingly. The cheapest, highest-yield bug-finding instrument in my entire project was my phone and my eyes.

Everybody already knows this one. Which is why depth two is the dangerous one.

Depth two: it cannot doubt me

Depth one is visible. You look at the screen and you see it. Depth two is invisible, because everything is green.

I had a matching rule wrong in my head. Not subtly - just plainly wrong, one of those business rules you’re sure you know until someone makes you say it out loud. I wrote the spec. The agent wrote a test that encoded my rule, then an implementation that satisfied the test.

// both users return to the pool unaffected
expect(scoreAfter).toBe(scoreBefore);

Green. Perfectly, confidently green, across a feature that was broken in four different ways.

And look at how the test was built:

const state = { ...base, matchPending: true, mutualPass: true };

A hand-constructed state that real usage cannot produce, asserted on directly. The actual flow was never driven through the real machine, so green meant nothing at all.

This is the failure red-green-refactor cannot catch, because the loop’s founding premise is that the test is right. When one agent writes the spec and the implementation in a single pass from a single mental model, that premise is the one thing nobody is checking.

A human reviewer might have caught my rule. A human team would have definitely found it.

Which is why “just have the AI generate the tests and review them” is such a seductive trap. It sounds like TDD. It removes the only independent author in the loop.

Depth three: all agents have the same opinions

The fashionable answer to “can I trust one agent” is: don’t use one. Fan out. Spawn eight reviewers from eight angles and call it a diversity of opinion.

They’re all still in an echo chamber.

My transcripts say otherwise. In one review, 5/8 supposedly independent angles converged on the same bug. Two angles with different roles returned a byte-identical false positive - same finding, same phrasing, both wrong. The only stage that caught it was the one I’d explicitly instructed to attack the findings.

Which makes sense - it’s the same model, with the same context and the same blind spots. A fan-out isn’t eight opinions. It’s one opinion, repeated, louder. A human team disagrees because its members have different scars, they’re different people. My agents agreed because they have one scar. They’re not truly opinionated.

So I tried to manufacture a different scar. One agent, fresh context, a deliberately adversarial persona, no access to the session that wrote the code, based on a former colleague I used to have good technical debates with. Its entire job was to attack the house position. It dismantled one of my own hard rules with two arguments I couldn’t refute, and I ended up deleting nine test-driver files and repealing the rule for an entire layer.

Variety of incentive beats variety of prompt, every time. And note the shape of that result: the only part of my system that has ever successfully argued with me is the part I built specifically to disagree. Everything else was built to comply.

At depth one it can’t see my taste. At depth two it inherits my errors. At depth three it confirms them, in parallel, with citations.

So: is the machine reliable enough to be left alone? No. Not at any of the three depths, and not for the same reason at any two of them.

What that leaves of the cycle

Put the depths back against red-green-refactor and ask the honest question: is a cycle designed for human limits even the right instrument here?

My repo has a partial answer, and it’s not the one I expected. I run identical cadence everywhere - commit at every green, review at every close. The domains respond completely differently. The pure matching engine got built fast, in tight commits, converging after one review round. The UI layer took roughly six times longer per commit and three review rounds, every time.

The discipline was uniform. Its yield was not.

The variable isn’t care. It’s verifiability. For pure logic, green and correct nearly coincide, so a machine can tell the agent it’s wrong and the loop runs at machine speed. For UI, green means the test environment is satisfied, and the test environment was never the thing that was broken - so every increment eventually waits on human eyes anyway.

That maps exactly onto the depths. Where a computational check can catch a lie, the cycle works and you can loosen the leash. Where the only working sensor is you, the cycle is decoration on a process whose real gate is a human looking at a screen.

So here’s what I now think red-green-refactor becomes:

Cadence follows verifiability. Loosen it where a machine can prove the agent wrong. Tighten it where you are the only sensor.

The loop’s unit changed too. A small step used to mean “what I can hold in my head.” Now it means “how much I’m willing to pay for and review before finding out the direction was wrong.” Same cycle, completely different justification. Small diffs are no longer about cognition. They’re about blast radius.

And here’s the part I can’t resolve, at least not yet. On UI, on animation, on anything at depth one, red-green-refactor produces green tests and a bad product, and the review stage catches everything anyway. In those stretches I run a discipline that costs a lot and demonstrably prevents nothing. I don’t have a fix for that. I have a scope: the cycle is worth its cost exactly where a machine can verify the result, and it’s a ritual everywhere else. Knowing which half of your codebase you’re in is most of the skill.

So who is the cage actually for?

Line the evidence up and it all points one direction.

The tests never caught the agent. Reviews and my own hands caught everything. The one catastrophic failure was my own wrong spec, and a cage built from my specifications cannot possibly catch my specifications. The rules file is my frustrations, transcribed. The only part of it that ever changed my mind was the part I built to fight me.

The agent was never the undisciplined one. It does exactly what it’s told, at 5am, forever, without resentment. It pays the ceremony of a fluent builder chain at zero marginal cost. I’m the unreliable component here - the one with the wrong matching rule, the untested assumption, the mood.

The cage doesn’t catch the agent. It catches me. TDD with an agent is a discipline for the human, wearing the costume of a discipline for the machine. That’s why my audit found nothing pointed at the agent. And it’s why I’m keeping a smaller version of it anyway.

Use It Until You Can’t

So what am I actually changing?

Cutting ceremony without guilt. Those nine driver files were the first cut, not the last. When a discipline has to formally exempt itself from a third of its own surface, that surface was theater.

Keeping the net, honestly labeled. My tests are a regression net and a forcing function. They are not a tripwire, and I’ll stop expecting them to be. The suite is what let me move hundreds of lines with confidence, and “write the tests afterward” is a discipline almost nobody sustains unless something forces it.

Moving effort to where the bugs actually were. More cold-read review with fresh context. More time with the app in my hand. And one standing instruction I now consider the most important line in my whole setup: attack my spec before you implement it. Depth two is where I lose, and an agreeable agent is worthless exactly there.

One caveat so you don’t over-index on my numbers: one repo, one developer, one domain. Yours will differ. But the agent that wrote your code can run this same audit in an afternoon. Ask it for evidence, not reassurance - mine handed me the strongest argument against my own discipline I’ve ever read.

TDD isn’t dead in the agent era. Its job changed. The tests aren’t there to catch the machine, and the cycle isn’t there to slow down thinking that now happens at machine speed. What’s left is a regression net, a budget control, and a mechanism that forces one specific human to decide what “correct” means before a very fast, very agreeable machine makes it real.

Nothing in your harness fixes that. You do.

YAGNI applies to your process, not just your architecture. Ceremony is over-engineering. Audit your rituals the way you audit your dependencies.

Keep your ego out the door, and evolve.


메타데이터
post_id
032cc855281f
slug
keep-it-honest-the-evolving-role-of-tdd-in-the-new-software-world-032cc855281f
url
https://medium.com/@shushy245/keep-it-honest-the-evolving-role-of-tdd-in-the-new-software-world-032cc855281f
canonical_url
https://medium.com/@shushy245/keep-it-honest-the-evolving-role-of-tdd-in-the-new-software-world-032cc855281f
author_url
https://medium.com/@shushy245
status
ok
fetched_at
2026-08-07 19:26:27