Writing automated tests for software has always been a touchy subject for engineers, even experienced ones. Most teams often view this activity as an afterthought or really only start to consider it when the product has gotten some traction and money or brand reputation are on the line.
The reality is that if you don’t write tests early, you’re almost certainly not going to write them later. Kent Beck, a pioneer of modern software development practices including the Agile manifesto and Test-Driven Development (TDD), famously wrote: once you achieve running functionality, your primary goal as a programmer is complete, and the psychological momentum to go back and write tests simply evaporates.
We shouldn’t have to wait until production bugs start breaking things to implement tests. Tests are invaluable, and the benefits extend far beyond just catching obvious bugs. If you’re still unconvinced about investing the time to write tests, here are a few key reasons you should:
Thankfully, we live in an era with frontier coding models and autonomous AI agents. Modern LLMs are exceptionally good at parsing specifications and writing test code, meaning testing no longer has to feel like a tax on development speed.
In this article, we’ll learn how to apply Test-Driven Development (TDD) and explore how modern agentic workflows make TDD faster, more practical, and remarkably effective.
Test-Driven Development is a programming philosophy built around writing automated tests before functional code. You start by writing the tests that will verify the feature you’re about to build, and only then write the code that makes them pass. Instead of jumping head-first into coding, you move through a three-step loop known as the Red-Green-Refactor cycle:
It may sound strange to “fake the answer” like step 2 says. Here’s a scenario: imagine you’re writing a calculateTotal function for an e-commerce cart, and your first test expects an empty cart to total 0. Hardcoding return 0 in the function definition is enough to turn that test green. But if the value is hardcoded, the function doesn’t really work, does it? It doesn’t, and that’s fine, because green tests never claim that the implementation is complete. A passing test only means your current test suite can’t tell your code apart from a correct implementation. Add a second test like a cart with two $10 items that should total $20, and the hardcoded 0 fails immediately. To get back to green, you have to write the real summing logic. This is a process known as triangulation in TDD, which pushes you to think deeply about the problem and keep adding test cases until it’s generalized into real, flexible logic.
Now, I know what you’re thinking: a lot of this sounds unnecessary. Why write a test you already know will fail, or fake an answer just to watch it pass? When I first came across TDD, well before AI coding models, it looked like theatrics to me too. Looking at it again, especially with AI in the picture, I’ve started to see the benefits of this approach.
Writing the test first forces you to design from first principles. You think about how the code runs, what goes in, and what comes out, before committing to an implementation. It also keeps you focused on building one feature at a time. This matters even more with AI agents, which can easily drift as problems arise.
TDD also has some cons, and they’re a big part of why so many teams abandon TDD or never start.
Ultimately, TDD isn’t the be-all and end-all solution for automated testing. Even Kent Beck wrote in his TDD book:
TDD can be used as a way to strive for perfection, but that isn’t its most effective use. If you have a big system, then the parts that you touch all the time should be absolutely rock solid, so you can make daily changes confidently. As you drift out to the periphery of the system, to parts that don’t change often, the tests can be spottier and the design uglier without interfering with your confidence.
The costs were never about whether TDD works. They’re about how much effort each cycle takes, and a feature takes a lot of cycles. That effort is what has changed. You still decide what behavior matters and what a failing test should prove. But an AI agent can now take on most of the heavy lifting around those decisions: setting up the harness, running the loop, and chasing down reds that fail for the wrong reason. The principles that follow are about splitting that work well.
Now that we have established the philosophy behind Agentic TDD and how the human-AI relationship shifts, let us look at the practical mechanics. Below are useful tips for putting Agentic TDD into practice.
Whenever you sit down for a coding (or vibecoding) session, you usually have a rough idea of what you want to accomplish. You might be tempted to dive straight into writing code or prompting an AI model, but in doing so, you’re not giving yourself enough time to think through the problem. TDD advises that your very first step should be to organize those thoughts into what Kent Beck calls a Test List.
A Test List is a collection of the behaviors, requirements, and scenarios you want to verify as you work through a problem. Curating this list forces you to consciously think through the problem and put everything into context: domain knowledge, business rules, edge cases, and potential loopholes before touching any implementation.
It’s important that you complete this step yourself before bringing an AI model into the loop. You should never let an agent spearhead this stage because you have more context and domain knowledge than the agent is likely to have. Only after you’ve written down your thoughts in a document or Markdown file should you invite the agent to review your list, spot oversights, and suggest additions. Giving the agent a pre-existing list also provides better context about what you’re building and how you intend to test it.
You should also treat your Test List as a living document. As you implement code and turn red bars green, solving one problem will naturally spark new ideas or reveal hidden edge cases. When those thoughts come up, update the list and prioritize the tasks that need your attention. Here’s how I format mine:
# Now - [X] Verify add to cart functionality works - [X] test for adding a single item - [X] test for adding an out of stock item - [X] test for updating quantity when item already exists - [ ] Verify checkout discount calculation # Later - [ ] Add product reviews - [ ] test that only users who purchased a product can review it
Personally, I treat this file as part of my development harness. I keep it in the project and make the coding agent aware of it, so we can collaboratively update the list and check off tasks as the implementation progresses. If you’re unsure where to start, a regression test to protect existing functionality is always a good option.
An agent can only run the TDD loop if your test suite is set up correctly. Before TDD begins, your test tools should already be installed, configured, and capable of running a trivial test. Otherwise, the agent’s first red will likely be a configuration error rather than a failing assertion.
Once the test suite works, your next focus is configuring your agentic coding environment (harness) with enough context and tools to reliably practice TDD.
Here are the things I make sure are in place:
pnpm test, vitest run), how to seed test data, and environment rules such as which database tests are allowed to touch. Anything you’d otherwise have to repeat in every prompt probably belongs here. Giving the agent this context upfront helps it understand how the project is structured and prevents it from inventing its own conventionsAGENTS.md with information that only applies to a particular workflow. For example, Matt Pocock’s collection of agent skills includes a TDD skill that provides instructions for following the TDD cycle. Anthony Fu, the creator of Vitest, also has a Vitest skill for framework-specific guidanceAGENTS.md as well, so the agent consistently follows your preferred practicesThe goal is to equip your agent with enough information so it carries out TDD effectively, in a way that aligns with your beliefs.
There’s a lot to learn about writing good tests, and testing practices are a topic of their own. Rather than cover everything, here are some industry practices that can make your TDD workflow faster, more maintainable, and effective, especially when working with coding agents.
beforeEach, beforeAll, afterEach, and afterAll deliberately to prepare and clean up test data, resources, and database updates. Make sure tests leave the environment in a known state and don’t pollute subsequent tests. Avoid relying on test order or shared mutable stateRefactoring is an important stage of TDD, but it also has the least supervision. Tests confirm that behavior still works, but they don’t tell you whether the new structure is actually better. That’s where guardrails come in. They check the code itself and fail when it drifts from your codebase’s standards. They’re part of the test harness we discussed earlier, but while the harness helps get the output right, guardrails catch and correct structural problems afterward.
Here are some guardrails you can use so your agent generates better, production-ready results when refactoring:
any. State in AGENTS.md that guardrail configs are yours to change and that the agent must refactor to pass, never suppress. Require a written reason on every disable comment (the eslint-comments plugin can enforce this), so suppressions can’t hide. Loosening a guardrail to pass is the same move as weakening an assertion to passThere are also some anti-patterns no tool will flag. If a simple test needs a hundred lines of setup, or tests break after unrelated changes, treat it as a design problem and have the agent raise it.
You’ve got a passing test suite now, good. But how do you verify the quality of the tests? Especially if you have a large set of tests and don’t have the time to review all of them (at least the less critical ones). Coverage reports can also be misleading, as it could be high but not testing the right things.
Introducing a secondary “auditor” agent to cross-examine the primary agent’s work can be an effective way to iron out any wrinkles in your test suite. This creates a clean separation of concerns in your workflow. While the builder agent runs through the TDD cycle to implement features and make tests pass, the auditor agent reviews those tests with a critical eye, searching for missing edge cases, false positives, and weak assertions that let bugs slip through unnoticed.
A classic technique software engineers use to vet tests is mutation testing. This process involves planting small bugs in the production code, such as flipping a > to >= or returning a default null, and rerunning the suite to see what existing tests break. The auditor agent can perform this task for you to spot gaps and then triage a solution. The mutation-test-suite-optimizer skill is one package built for this process. It also natively supports StrykerJS for JavaScript and TypeScript, and it sorts surviving mutants into causes such as weak assertions, missing boundary checks, and unchecked side effects. Matt Pocock’s code-review skill is an alternative that reviews your changes against your coding standards and the original spec in separate sub-agents. Although it isn’t built for auditing tests, it can still act as a good reviewer.
A good prompt is enough to trigger the whole phase, but make sure to run it in a fresh context so traces from the TDD agent don’t influence its reasoning:
Use the mutation-test-suite-optimizer skill to audit the test suite. Run StrykerJS locally. Report findings only. Don't change any tests.
To see these principles working together, let’s build two small features in a demo storefront. Clone the agentic-tdd-shop repo to follow along from the same starting point. It’s a mobile-first Next.js 16 store with Postgres, Better Auth, Vitest, Testing Library, and Playwright. The harness from this article is already in place: I have a local test database running, guardrails behind a single pnpm guard command, mutation testing with StrykerJS, and the TDD, Vitest, and mutation skills installed in .claude/skills/.
This app and the features we’ll build are deliberately lightweight, so the focus stays on the process rather than the code. The store has auth tables for authentication and cart items. There’s also a deliberate Broken Object Level Authorization (BOLA) bug in the code, which allows a shopper to remove cart items from another shopper’s cart. Here’s what the test list looked like at the start:
# Now - [ ] Sign up and sign in return the shopper to the right page - [ ] test signing up lands on the page the shopper came from, with no error shown - [ ] test signing in with valid credentials lands on the page the shopper came from - [ ] test a wrong password shows an error and stays on sign-in - [ ] A shopper can only remove the items in their own cart - [ ] test removing your own cart item takes it out of your cart - [ ] test removing another shopper's cart item leaves it in their cart
The rules the agent follows live in AGENTS.md. Although the TDD skill covers most of these instructions in detail, we also tell the agent about our guardrails command:
1. Red. Write one failing test for the current item. Run it and confirm it fails 2. for the expected reason: the behavior is missing. A failure caused by imports, 3. config, the database or tooling is not a red. Fix the harness, rerun, then continue. 4. Green. Make it pass with the smallest change. 5. Refactor (if necessary). On green only, improve the structure without changing what it tests. 6. Verify. Run pnpm guard and the tests after each change.
You can then start the TDD cycle with a simple prompt. Here’s what I used:
We're building the features listed in ./test-list.md in TDD. Use the TDD skill to work through the items on the list one at a time.
The agent opened by acknowledging the TDD skill and the specific instructions in AGENTS.md. For each test item, it proposed test seams (a seam is an entry point in the code that lets you change program behavior without editing production code directly) and then began the cycle. Here are some notable moments from implementing the test list:
removeCartItem didn’t accept a shopper yet, while the BOLA test exposed how an intruder could empty the owner’s cart. One line, where: { id: itemId, userId }, fixed bothCartView past the guardrail’s 80-line limit. Instead of raising the limit, the agent extracted a CartLineItem component. Then came the cleanest no-red moment of the run: “I expect this to pass immediately… if it does, I’ll break the code to prove the test can fail.” When it passed, the agent changed available: product.stock > 0 to false, confirmed the failure, and restored the codeAGENTS.md only allows ticking it after the reviewer agent runsThis is where mutation testing gets interesting. With every item’s tests green, I started the reviewer agent in a fresh context, using the prompt from section 5 of AGENTS.md: run mutation-test-suite-optimizer locally against the changed files, report only, no fixes.
The report covered two files that govern the cart logic: cart-context.tsx and cart-view.tsx. The code this TDD run added scored 100%, meaning every deliberate break in it was caught. The existing code in those files scored between 36% and 45%, because nobody had written tests for it yet. These were the main gaps (survivors) the mutation testing found:
id from the delete query: the only removal test had exactly one item in the cart, so deleting one row looked identical to deleting all of them> to >= at the stock boundary: nothing had tested a cart holding exactly as much as was leftWith this review, the agent added new items to the test list to cover these edge cases. For each one, it wrote the test the reviewer described, confirmed the test passed, then manually reproduced the exact mutant to confirm the test failed, and restored the code.
At the end, I had 30 Vitest tests and 9 Playwright tests passing, pnpm guard was clean, and every item’s parent box was ticked. The final result is found in the tdd-run branch on the repo. Here’s the final test list:
# Now
- [x] Sign up and sign in return the shopper to the right page
- [x] test signing up lands on the page the shopper came from, with no error shown
- [x] test signing in with valid credentials lands on the page the shopper came from
- [x] test a wrong password shows an error and stays on sign-in
- [x] `safeRedirect` (unit) only returns same-site paths
- [x] test a missing redirect falls back to /
- [x] test a relative path with a query string is kept
- [x] test absolute and protocol-relative URLs (https://, //, /\) fall back to /
- [x] test auth pages as the target fall back to /
- [x] test (e2e) signing in with an off-site redirectTo lands on /
- [x] test (e2e) signing up with an off-site redirectTo lands on /
- [x] (review) test a repeated redirectTo (an array) falls back to /
- [x] (review) test (e2e) signing in from a product page returns to that product page
- [x] A shopper can only remove the items in their own cart
- [x] test removing your own cart item takes it out of your cart
- [x] test removing another shopper's cart item leaves it in their cart
- [x] (review) test removing one of two items keeps the other
- [x] (review) test (e2e) removing an item on /cart keeps it gone after a reload (proves it reached the server)
- [x] (review, refactor) assert the untouched owner's line by its literal product and quantity, not by an earlier getCartLines result
- [x] Flag cart items that are no longer available
- [x] test a cart item whose product sells out shows "No longer available" and is left out of the subtotal
- [x] test a cart item with more units than are left in stock drops to quantity 1
- [x] test an in-stock cart item is not flagged (added: nothing checks `available: true` yet)
- [x] (review) test a cart holding exactly the stock (3 of 3) keeps its quantity
- [x] (review) test reading the cart shows only the shopper's own items (own products, not the shared seeded mug)
- [x] Never sell more than is in stock
- [x] test adding a one-unit product twice keeps only one in the cart and disables the UI button
- [x] (review) test adding an in-stock product twice gives quantity 2
- [x] (review) test adding a second product makes its own cart line
- [x] (review) test another shopper adding the same product leaves your quantity alone
- [x] (review) test another product at its stock limit doesn't disable this product's button
## Later
- [ ] Count only available items in the floating cart
- [ ] test the floating cart count leaves out items that are no longer available
- [ ] Store the stock cap on the cart row, not only when reading
- [ ] test a capped cart row is saved with quantity 1, so adding again doesn't grow it unseen
- [ ] Test older cart code the review found unchecked
- [ ] test adding while signed out sends the current page as the sign-in return target
- [ ] test a cart line shows its own total and "× n" when quantity is above 1
- [ ] test the cart header count and the empty-cart state
TDD never failed because the idea was wrong. It failed because each cycle cost more effort than most teams could afford: the setup, the repetition, and the debugging of reds that failed for the wrong reason. Agents absorb most of that effort. What’s left is the part only you can do: deciding what matters, writing it down as a test list, and agreeing where the tests should live.
Get that part right, and the rest of the harness keeps the agent TDD-aligned. The database catches real bugs, guardrails hold the refactor to your standards, and a second agent checks whether the tests are worth trusting. None of it needs to be perfect on day one. Start with one small feature, one test list, and one red you’ve confirmed fails for the right reason, and build from there.

We tested five AI code review tools for speed, setup, security, and code quality to compare their strengths and help you choose the right tool.

Compare five React toast libraries for features, bundle size, accessibility, performance, and React 19 compatibility.

See how Tecovas built a patching agent that turns LogRocket’s Galileo AI issue analysis into GitHub PRs, with a developer reviewing every fix before it ships.

Explore OctaneJS, a compiler-first alternative to React. Build a task dashboard and learn about rendering, hooks, asynchronous UI, and React compatibility.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now