All articles in AI coding agents
AI coding agents

Test-first development with coding agents

Why agents need tests written before the code, the six-step loop, how to prove a test can fail, and how to stop an agent weakening the check it must pass.

7 min read

A coding agent stops when its check passes. That makes the check the most important thing you give it, and it makes the order of work matter more than it does for a person. Tests written after the code tend to describe what the code does. Tests written before it describe what the code should do, and an agent that has to make them pass is working toward your intent instead of its own.

This article covers the test-first loop with a coding agent: how to get tests that can fail, how to stop the agent from weakening them, and the guard tests that protect rules your documentation only claims.

Why test-first matters more with agents

Test-driven development has always been about design and confidence. With an agent it is also about control. When the agent writes the code first and the tests afterwards, three things go wrong.

  • The tests encode the implementation. They assert what the code happens to return, bugs included, and they pass by construction.
  • The check becomes circular. The agent verifies its work against tests it wrote to match that work.
  • Gaps go unnoticed. Behaviour the agent did not implement has no test, so nothing fails.

Writing the tests first, from the acceptance criteria, breaks the circle. Anthropic's guidance for Claude Code describes a stronger version of the same idea for parallel sessions: one session writes the tests, and another writes the code that makes them pass.

The loop

The loop has six steps, and the order is the point.

  1. Write tests from the acceptance criteria. At least one per criterion, named so that the mapping is obvious. No implementation yet.
  2. Run them and watch them fail for the right reason. A test that fails because an import is missing proves nothing. It should fail on the assertion about the behaviour.
  3. Commit the tests on their own. This fixes the contract and makes any later change to the tests visible in the diff.
  4. Implement until they pass, without editing them. If a test looks wrong, the agent stops and says so; it does not change the test.
  5. Run the wider suite, the type check and the linter. New behaviour that breaks old behaviour is not done.
  6. Return the evidence. The commands that were run and their output, in the pull request.

In our experience, steps 2 and 3 are the ones agents skip unless asked. Ask.

Tests that can fail

The most dangerous test is one that cannot fail. It passes today, it will pass after the next regression, and it makes the coverage report look better than the code is. Agents produce them for predictable reasons: asserting on a mock rather than on behaviour, looping over a list that turns out to be empty, catching the very exception the test was meant to detect, or comparing a value with itself by two routes.

Two habits catch most of them.

Assert that there is something to check. A test that loops over a set of records should first assert that the set is not empty. Many guard tests in Stride's repository open this way; the one that checks our comparison roundups, for example, first asserts that there are roundups, tool picks and Stride picks to check, because an empty set passes every rule.

Prove the test can fail. Before trusting a new test, break the code it protects and run it: delete the line, invert the condition, drop an item from the list. It should go red. Then revert the change. This is mutation testing done by hand, one test at a time, and it takes about a minute. It is also a fair thing to ask an agent to do and to report in the pull request.

Keeping the agent away from the tests

Instructions help, but they are not enough. An agent under pressure to make a check pass will sometimes edit the check, skip it or update a snapshot, and occasionally explain convincingly why that was the right thing to do. Three layers keep it honest.

  • Say it in the story and in the instruction file. "Existing tests may not be edited or deleted. If a test looks wrong, stop and report it."
  • Make it visible. Committing the tests first means any later change to them shows up on its own. In review, read test changes before code changes. Treat regenerated snapshots with particular suspicion: regenerating a snapshot is how a regression becomes the new expected output.
  • Enforce it where it matters. Claude Code's hooks run deterministically, unlike the advisory instructions in CLAUDE.md, and a hook can block edits to protected paths. CI can reject a pull request that changes a story's tests and its implementation together, if your process wants that separation.

Guard tests for the rules your documentation claims

Some of the most valuable tests do not test a feature at all. They pin a rule the codebase depends on, one that prose alone cannot keep true.

Stride's repository has many, and several exist because documentation drifted. A content module's header once claimed that every tool in our ranked lists was verified at build time. Nothing verified it, and a mistyped entry silently disappeared from a page while the page's structured data still counted it. The fix was a test, not a better comment. Similarly, the number of tools Stride's MCP server offers is now derived from the tool registry by a test, after a check found the website stating a different figure from the one the server returned.

For agent work this matters twice over. Agents read your documentation and trust it, so a stale claim in a README becomes a wrong assumption in their code. And agents change code quickly enough that a rule held only in prose will be broken by the one change nobody remembered it applied to. When a rule matters, move it into a test.

When the behaviour is hard to test

Some stories resist unit tests: visual polish, copy, behaviour that depends on a third-party service. The loop still applies with a different check.

  • For user interfaces, give the agent a screenshot to match and ask for screenshots of the result.
  • For integrations, test against a recorded fixture or a local fake, and keep one end-to-end test against the real service in a separate job.
  • For content, write the rules down as tests where you can: length limits, required disclosures, forbidden claims, and counts that must match the data.

If none of these is possible, the story needs a person to verify it, and it should say so.

What to ask for in the pull request

A test-first pull request from an agent should show:

  • The list of acceptance criteria, each with the test that covers it.
  • The output of the new tests failing before the implementation, and passing after it.
  • The output of the full suite, type check and lint on the final commit.
  • Any test the agent believes is wrong, reported rather than edited.

That evidence turns review from re-running everything into checking a few things, which is the subject of reviewing pull requests from coding agents.

Stride's quality gates check a sprint, release or test suite against pass rate, open critical defects and test coverage, and coding agents can create test cases from acceptance criteria and record their results over MCP.

See quality gates in Stride

Frequently asked questions

Should a coding agent write its own tests?
Yes, as long as it writes them before the code, from the acceptance criteria, and does not edit them afterwards. Tests an agent writes after its own implementation tend to assert what the code does rather than what it should do. Anthropic's guidance for Claude Code also suggests having one session write the tests and another write the code.
How do I stop an agent from editing tests to make them pass?
Say so in the story and the instruction file, commit the tests before the implementation so any change to them is visible, read test changes first in review, and where it matters enforce it with a hook or a CI rule rather than relying on the instruction alone.
How do I know a test can actually fail?
Break the code it protects and run it: delete a line, invert a condition or drop an item, and confirm the test goes red before reverting. For tests that loop over data, first assert that the data is not empty, because an empty set passes every rule.
What if the feature is hard to unit test?
Change the check rather than skipping it: screenshots for user interfaces, recorded fixtures or local fakes for integrations, and rule-based tests for content. If no automated check is possible, the story needs a person to verify it and should say so.