cd /news/ai-agents/practices-i-abandoned-with-agents-an… · home topics ai-agents article
[ARTICLE · art-125693] src=adamtornhill.substack.com ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Practices I Abandoned with Agents: An Ode to Test-Driven Development

A veteran developer with 25 years of test-driven development experience describes abandoning TDD after transitioning to agentic coding, arguing that forcing AI agents through increments optimized for human cognition negates many of the benefits. The developer, who founded the code analysis startup CodeScene, says agentic coding is sufficiently different from manual coding that existing development principles and practices need to be questioned.

read10 min views1 publishedSep 10, 2026
Practices I Abandoned with Agents: An Ode to Test-Driven Development
Image: Adamtornhill (auto-discovered)

Test-Driven Development (TDD) has been my default programming mode for 25 years. TDD simply made me a better developer. It’s been invaluable.

However, the time has come to bid farewell to my old companion. Agentic coding is sufficiently different from manual coding, meaning we need to question existing development principles and practices. And TDD didn’t survive my transition into AI-native development: forcing an agent through increments optimized for human cognition negates many of the benefits.

This article reflects on my decades as a TDD practitioner as well as the agentic characteristics that made me abandon the technique. As customary, we’ll start from the beginning.

TDD: the backstory #

I was fortunate to stumble on TDD in the early 2000s. Back then, I used to hang around in various newsgroups and got in touch with some vocal TDD proponents.

At first, TDD felt strange to me. So backwards. But I did understand that the limitation was most likely on my side, and decided to give it a go. It took plenty of practice, but I was soon hooked.

Since then, I’ve written articles about TDD, and even used to teach courses and workshops in the early 2010s.

The only thing that never resonated with me was the name. TDD isn’t really about testing, as its name would imply. Rather, it’s a design technique with low-level verification as a side effect.

That aspect is also what makes it hard. The TDD workflow itself is simple, but the technique also puts relentless pressure on your software design. Poor design choices diminish your returns, and you’ll inevitably find yourself with code that’s hard to change. Worse, there’s now more of it since you have poor-quality test code, too.

As such, it took me a couple of years to become good enough at software design to pull off TDD effectively. Once I got there, TDD proved its value. Over and over again.

Hitting the big wins #

I remember one project in particular. I had just switched jobs and was now working on a large simulation platform. A month into the job, my manager told me that we had a new project coming in. There was only one problem: the project had to be shipped within four weeks. “I know that it isn’t really doable, but the money is so good that we have to try.” Those were the words I got from management.

Why me, then? Well, I was the new guy, so the only one without any current responsibilities.

The domain was non-trivial. We had to integrate and simulate communication with a real radar station, including implementing full support for a safe air traffic management standard.

So I went to work and used TDD as the design technique. What I always liked about TDD is that it gives me a workflow where progress is known. Like: this part works, and here’s what it does. That allowed me to iterate rapidly. In fact, I finished a bit early and took the extra time to implement a simulator for the radar station, too. Now we could connect the two simulations and test the system end to end.

The big win came a couple of days later when our project lead and system specialist travelled abroad to take part in a two-week on-site integration session with the customer. A few hours later, my project lead phoned and said: “I don’t know what you did, but we installed the software, and it just worked.” No need for any two weeks of painful “integration.” I wasn’t surprised. This is the way software is supposed to be.

Now, I don’t tell this story just to brag about how great I used to be. (Although that feels nice to reflect on, too.) Rather, I want to emphasize that TDD was the game changer that took me from a decent to a good developer. TDD stayed with me for many years, and used to be my default way of coding.

Going Functional: the dial down on TDD #

Fast forward ten years, and I found myself in a different context. I had recently quit a well-paid job to focus on my own startup: CodeScene. The idea was to take the techniques from Your Code as a Crime Scene, automate them, and make them accessible as a SaaS.

There’s much to love about the early days of a startup. For once, I was free to choose whatever technology I wanted. (It’s of course also scary because there aren’t any excuses, nor someone else to blame if things turn south.) I decided to build CodeScene in Clojure.

There’s a whole talk reflecting on how that tech choice worked out here, so I’ll keep the motivation brief. Let me just state that there were three main reasons to go with Clojure:

  1. Clojure is ridiculously productive. I knew I had to make each hour count.
  2. Clojure is fun. I knew that a startup means long hours, so I wanted to ensure I spent all that time doing something enjoyable.
  3. The Clojure community is great. Clojure wasn’t mainstream, meaning most programmers who knew it had learnt the language out of personal interest and intrinsic motivation. That’s exactly the quality I was looking for when recruiting the team.

In the early days, my Clojure development flow pretty much followed what I was used to from previous C++/C#/Java/Python work. I used TDD for everything.

However, I soon noticed that TDD wasn’t as optimal in a Clojure context. Clojure is a highly dynamic language with a powerful REPL. As such, I found it much more productive to experiment in the REPL. Once I had a working piece of an algorithm or solution, I then copied my REPL session to a new file and re-shaped that code to unit tests. Those tests served me well for the next iteration.

That REPL-driven development process has been my default for any novel or exploratory work. (And there is plenty of that when developing a new product.) But TDD still stuck with me: a lot of software development is maintenance, including extensions and improvements to existing features. For those types of tasks, TDD was still a great fit even for Clojure.

Adapting to the next paradigm shift #

Functional programming (FP) forced me to re-evaluate my development patterns. Developing with a REPL allowed me to move faster. Part of what makes TDD work is that it allows us to simulate a fast feedback loop in languages without a REPL. With a REPL, we get even faster iterations. If I had stuck to my guns and stayed with my previous workflow, I wouldn’t have been able to gain that extra productivity boost.

Moving to coding agents and AI-first was a similar paradigm shift that I had to acknowledge and adapt to.

When I took my first agentic steps in September of 2025, I pretty much followed my development process from the previous paradigm. I applied my agents in a tight inner loop, developing the code very iteratively in a bottom-up style with passing unit tests as the progress marker.

Just like with my FP shift, I soon noticed that this wasn’t effective. Sure, it worked and it felt familiar. But I wasn’t getting any real benefits, aside from maybe faster coding, which was never the bottleneck in the first place anyway.

Instead, I had to rethink both my development flow as well as how I view code. Again.

Agentic coding: exit TDD #

The challenge with agentic coding is that we don’t have a community with years of experience to tap into. We are all beginners. As such, it took me 6-8 months to find a development process that allowed me to keep risk in check while still reaping the benefits of shorter development cycles.

I have written about my AI-first development process here, so I’ll keep details light. But basically, I no longer focus on individual building blocks. Rather, agents allow me to iterate on the whole system at the feature level. That characteristic is where the bulk of agentic gains come from.

To pull that off, I put most of my effort into defining good human/agent abstraction boundaries. This typically implies tests. More specifically, end-to-end tests. Here’s how I captured that process in my previous post:

Starting with e2e tests solves the validation problem: how do I ensure that the AI generates the right code? With the e2e tests nailed down, I typically let the agent proceed and write the code that makes them pass. I rarely spend much time looking at the actual implementation.

So, in my agentic flow, tests come first. That doesn’t mean it is TDD, though.

The difference is the scope and purpose:

  • The agentic feedback loop is larger and (much) less granular. One agentic iteration would have spanned tens of red-green-refactor cycles with tons of small design decisions. Those are now done in one sweep.
  • I’m also no longer participating in that low-level loop. In fact, I don’t even care if app code or test code is written first.
  • Code is no longer for my consumption. My AI code doesn’t look like what I would have written myself. And I had to accept that. What matters is that the code supports machine reasoning and change; the machine is the primary audience. That I enforce via tooling, not manual inspection. (See this post for details).

TDD is a great technique for low-level design, and allows me — the human programmer — to incrementally design the code. It aligns nicely with human cognition, letting me tackle larger problems in small increments with visible progress.

For agents though, I can take larger steps. In fact, I need to take larger steps, otherwise there’s not much motivating the significantly more challenging and complex process that is agentic coding.

The surviving part of TDD #

Dropping TDD feels like a drastic step. But it’s similar to what happened years earlier as I went all in on Clojure. I had to adapt my habits and workflows to a new context in order to take advantage of it. Agents are similar, and continuing to work in ways optimized for manual programming is unlikely to give us benefits. Rather, it introduces drawbacks and new bottlenecks. (PR reviews anyone?)

That said, not all is gone. Even though I no longer use TDD as my primary design technique, there are parts of the TDD process that continue to carry value.

One such idea I kept from TDD is double-entry bookkeeping. I had so many situations where the AI acted on a failing test by either a) deleting that test, or b) changing the test condition to match the (erroneous) code. Hence, I always drive any change to the codebase via a failing test. The difference is that those tests can typically be at a higher level than unit tests.

This style of driving implementation via a failing test implies that the red-green principle of TDD survived, too. However, there’s a difference here as well:

  • In TDD, red verified that I had implementation work to do.
  • With agents, red gives me confidence that my test suite is capable of validating the changes I delegate to agents.

Further, the usual refactoring step is now left to the machine and driven by feedback from my MCP servers inspecting the code.

So, TDD lives on in spirit even in my AI-native workflows. It’s one of the great software design methodologies, and the fact that I found it useful for 25 years speaks to its quality. I’m forever grateful to Kent Beck for discovering the process.

── more in #ai-agents 4 stories · sorted by recency
── more on @codescene 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/practices-i-abandone…] indexed:0 read:10min 2026-09-10 ·