How do coding agents make TDD more practical?
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:
- Code confidence: A solid test suite can transform technical anxiety into confidence. Whenever you’re unsure about pushing an update to production, running your test suite can give you an instant sense of relief the moment you see test results turn green
- Refactoring without fear: Tests give you a reliable safety net (especially when letting AI modify your codebase), allowing you to make architectural changes without worrying about breaking existing business logic
- Production reliability & user trust: Testing upfront leads to clean code that works. Catching defects long before they reach QA or production builds customer trust and keeps revenue flowing
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.
What is TDD, and why can it be expensive?
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:
- Write a test (Red): Before any implementation exists, write a test for the exact behavior you want. Treat the underlying code as a black box and invent the interface you wish you had: the function name, its inputs, and the output that proves it works. Then run the test and check that it fails for the reason you expect: not a typo or a broken setup, but that the behavior is missing
- Make it pass (Green): Get the failing test to pass as quickly as possible, even if that means faking the answer
- Clean it up (Refactor): With the tests passing, improve the code. Remove duplication, rename things, simplify the structure. Refactoring changes how the code is written, never what the tests verify. This phase is optional
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.
- The loop itself: Every cycle means writing a test, running it, reading the failure, writing code, and running everything again. A ten-line signature check for a payment webhook took three full cycles on its own. Across a whole feature, doing that by hand starts to feel slow, and in an agentic context it can prove expensive as it burns more tokens with the repeated steps
- Over-indexing on test coverage: A lot of TDD advocates put so much emphasis on test coverage, but coverage does not equal quality. High statement or line coverage can be a dangerous quantitative illusion. A test suite can visit 100% of execution paths while making shallow assertions or verifying nothing of substance
- Not all software problems fit TDD: TDD assumes software behavior can be cleanly isolated into deterministic inputs and expected outputs. Specialized domains such as concurrency and security protocols are notoriously difficult to test-drive
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.
How to use AI agents for Test-Driven Development
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.
1. How to create a test list for agentic TDD
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.
2. How to set up a testing harness for AI agents
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:
- Set up the testing tools: Whether you use Vitest, Playwright, or another framework, make sure it can run a trivial test cleanly before starting TDD. The agent should be able to go straight into the red-green-refactor loop rather than debugging your test setup
- Use AGENTS.md to describe the project: This is where you’d keep instructions that apply across the project: the architecture and folder structure, naming conventions, where tests live, how to run the test suite (
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 conventions - Use skills for targeted workflows: Skills let you give an agent specialized instructions without bloating
AGENTS.mdwith 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 guidance - Point it toward proven practices: Reference testing resources you agree with, such as Yoni Goldberg’s javascript-testing-best-practices and nodejs-testing-best-practices. This gives the agent a set of proven patterns to draw from instead of relying on whatever generic testing approach it was trained on. You may also have strong opinions about testing yourself. Distill those into
AGENTS.mdas well, so the agent consistently follows your preferred practices
The goal is to equip your agent with enough information so it carries out TDD effectively, in a way that aligns with your beliefs.
3. Which testing practices improve agentic TDD?
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.
- Use Arrange-Act-Assert: Structure tests into three clear stages: arrange the data and dependencies, act on the code under test, and assert the expected result. This gives each test a predictable structure that makes it easier for both humans and coding agents to understand what is being tested
- Test behavior, not implementation details: Focus on public inputs, outputs, and observable behavior rather than private methods, internal state, or specific implementation choices. This makes tests more resilient to refactoring and allows an agent to restructure the code without breaking tests that still describe the correct behavior
- Assert through the user’s path: Verify results through the API endpoint or page the user would interact with rather than querying the database directly. A database query can confirm that data was written, but it skips the application logic responsible for reading and presenting that data directly to the user
- Use an isolated, reproducible test database: Keep your test database separate from production. A local database is usually the simplest option: it keeps tests fast and isolated, avoids network dependencies, and lets you freely create and destroy test data. Ideally, your test command should provision or start the database automatically, so the suite doesn’t depend on someone remembering to start a service. Docker Compose can help with this. If your team needs to retain test results, a dedicated test database is a reasonable alternative
- Each test should act on its own records: Each test should create and operate on its own records rather than relying on data created by another test or pre-seeded data. This makes the test story easier to follow and removes ambiguity about where its data comes from. Only pre-seed data shared across tests, such as constants or a common test user. Everything a test needs to verify should be created within that test, keeping tests independent of execution order and making failures easier to reproduce
- Be deliberate about setup and teardown: Use
beforeEach,beforeAll,afterEach, andafterAlldeliberately 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 state - Mock external dependencies: Replace calls to third-party APIs (like payment providers) with mocks in your tests. This gives you full control over the responses, so you can test success, errors, and edge cases on demand. Your tests also won’t fail because of network delays or a service being down, and you won’t burn through API rate limits or usage quotas just by running your test suite. Tools like MSW and Nock intercept network requests at the boundary, so your code runs as normal without ever reaching the real service
- Don’t test what other tools already cover: Focus your tests on the logic and business rules you own rather than retesting libraries and frameworks. Similarly, avoid writing tests for issues that TypeScript, ESLint, or security scanners can already catch. Use your tests for application-specific rules instead, such as rejecting an unsigned webhook
4. How to use guardrails to prevent refactoring anti-patterns
Refactoring 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:
- A type system: A type system is the easiest signal to an agent for bad code. TypeScript in strict mode catches broken contracts between modules the moment a refactor moves code around
- Linters with limits: ESLint’s size rules (function length, nesting depth, parameter count, cyclomatic complexity) flag code that has grown too big to change safely. Most are off by default, so set the limits yourself
- Modularity rules: Dependency-cruiser enforces how modules may depend on each other: no UI component importing the database client, no circular dependencies, no orphaned files. Its rules file doubles as documentation of your architecture, one the agent can’t ignore
- Dead-code detection: Knip finds the files, exports, and dependencies a refactor left behind
- Protect the guardrails themselves: When a check fails, an agent’s quickest route to green is often to raise the limit, add a disable comment, or widen a type to
any. State inAGENTS.mdthat guardrail configs are yours to change and that the agent must refactor to pass, never suppress. Require a written reason on every disable comment (theeslint-commentsplugin can enforce this), so suppressions can’t hide. Loosening a guardrail to pass is the same move as weakening an assertion to pass
There 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.
5. How to use a second AI agent to audit tests
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.
More great articles from LogRocket:
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.
How agentic TDD works in a real project
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:
How an AI agent tests sign-in and sign-up redirects
- Browser-based testing: Seam agreed that Playwright should handle sign-in and sign-up like a real shopper, since a redirect only means something when the browser actually lands somewhere
- Catching false reds: Several times, the agent caught a test that passed immediately. While implementing the sign-in redirect, it noted, “This will probably pass straight away… if it does, it isn’t a valid red yet, so I’ll prove it can fail.” When the test passed, the agent deliberately broke the redirect, confirmed that both tests failed as expected, restored the code, and documented why: “It never had a real red. It passed on its first run because the code from the sign-up cycle already handled sign-in”
- Keeping scope focused: After the agent finished this item, I tested the flow myself in the browser, and everything worked as expected. I also spotted a header bug: it still said “Sign in” after I’d signed in. Rather than derail the current task, the agent added it to the later list: “Neither one belongs to the redirect item we’re on, so I’ve added them to the list as a new item rather than slipping them into this cycle.” It asked permission and continued with item 1
- Following TDD discipline: Catching false reds, anticipating when a test will pass, and deliberately creating a real failure before moving on is exactly the kind of discipline we want in TDD
How to test cart ownership, product availability, and stock limits
- Cart ownership (BOLA): Once item 1 was done, the focus shifted to removing only a shopper’s own items, flagging stock that sold out after being added, and preventing overselling
- BOLA testing: The agent acknowledged the planted BOLA bug and suggested adding a happy-path test to catch regressions. Both reds were real: the happy-path test failed because
removeCartItemdidn’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 both - Availability handling: This item had two seams: a data seam and a display seam. Adding the “No longer available” label pushed
CartViewpast the guardrail’s 80-line limit. Instead of raising the limit, the agent extracted aCartLineItemcomponent. 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 changedavailable: product.stock > 0tofalse, confirmed the failure, and restored the code - Stock-limit testing: The agent handled a server-side red for the increment logic and a UI red for the button’s disabled state using the normal testing process
- Reviewer sign-off: Every item ended the same way: tests green, but the parent box remained unticked because section 5 of
AGENTS.mdonly allows ticking it after the reviewer agent runs
How mutation testing reveals gaps in AI-generated tests
This 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:
- Dropping
idfrom 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 - Deleting the call that sends removal to the server: the UI removes items optimistically before the server responds, so a missing server call went unnoticed by every existing test. Two correct-looking halves were covering for each other
- Changing
>to>=at the stock boundary: nothing had tested a cart holding exactly as much as was left
With 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 ( //, /\) 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
Conclusion
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.
PakarPBN
A Private Blog Network (PBN) is a collection of websites that are controlled by a single individual or organization and used primarily to build backlinks to a “money site” in order to influence its ranking in search engines such as Google. The core idea behind a PBN is based on the importance of backlinks in Google’s ranking algorithm. Since Google views backlinks as signals of authority and trust, some website owners attempt to artificially create these signals through a controlled network of sites.
In a typical PBN setup, the owner acquires expired or aged domains that already have existing authority, backlinks, and history. These domains are rebuilt with new content and hosted separately, often using different IP addresses, hosting providers, themes, and ownership details to make them appear unrelated. Within the content published on these sites, links are strategically placed that point to the main website the owner wants to rank higher. By doing this, the owner attempts to pass link equity (also known as “link juice”) from the PBN sites to the target website.
The purpose of a PBN is to give the impression that the target website is naturally earning links from multiple independent sources. If done effectively, this can temporarily improve keyword rankings, increase organic visibility, and drive more traffic from search results.
Streaming Film
Nonton Film gratis
Jasa Backlink
Download Anime Batch