Who Checks the Tests an AI Agent Writes?

Who Checks the Tests an AI Agent Writes?
An agent writes a function, adds tests, runs them, and reports success. The implementation and the tests agree.
What established that they agreed on the right behavior?
This is the part of AI-generated testing I want to examine before counting new assertions. A test can be perfectly executable, exercise the relevant branch, and confirm the same mistaken interpretation that produced the code.
Humans do this too. The additional concern with an agent is how conveniently implementation, test cases, fixtures and the final explanation can be produced from one interpretation. They look like several pieces of evidence. They may have only one underlying assumption.
I don't want fewer generated tests. I want a clearer answer to where their expected results came from.
A passing test for the wrong rule
Consider a fictional checkout policy. Free shipping applies when the merchandise subtotal after discounts is at least 5,000 cents. Tax and shipping charges do not contribute. The examples below assume valid, nonnegative integer inputs, with discounts no greater than the merchandise subtotal; input validation is a separate concern.
This is an illustrative example, not an incident from a client project.
An implementation and its test might look like this:
import assert from 'node:assert/strict'; type ShippingQuote = { merchandiseCents: number; discountCents: number; }; function qualifiesForFreeShipping(quote: ShippingQuote): boolean { return quote.merchandiseCents >= 5_000; } assert.equal( qualifiesForFreeShipping({ merchandiseCents: 6_000, discountCents: 1_500 }), true, );
The test passes. The discounted subtotal is 4,500 cents. Under the stated policy, that order does not qualify.
The test has not failed to execute the implementation. It has asserted the wrong answer. A coverage report can show that the function ran without noticing that the expected value contradicts the requirement.
Adding more examples above the undiscounted threshold won't necessarily help. Nor will renaming the test to sound more specific. The missing ingredient is an expected result derived from the policy, rather than an observation of what the function currently returns.
That expected result is the test oracle: the basis for deciding whether the observed behavior is correct. It doesn't have to be a separate program or an expensive formal specification. Sometimes it is one arithmetic calculation and a sentence someone has actually agreed on.
Establish the expectation before polishing the implementation
For this policy, I would start with a few examples whose answers can be worked out without consulting the function:
assert.equal( qualifiesForFreeShipping({ merchandiseCents: 6_000, discountCents: 1_500 }), false, ); assert.equal( qualifiesForFreeShipping({ merchandiseCents: 5_000, discountCents: 0 }), true, ); assert.equal( qualifiesForFreeShipping({ merchandiseCents: 4_999, discountCents: 0 }), false, );
The first case disagrees with the original test. That disagreement is useful. It makes the interpretation visible instead of burying it beneath a successful run.
The implementation can then follow the policy:
function qualifiesForFreeShipping(quote: ShippingQuote): boolean { const discountedMerchandiseCents = quote.merchandiseCents - quote.discountCents; return discountedMerchandiseCents >= 5_000; }
This is not a complete checkout test suite. The cases establish the particular discount and threshold behavior under discussion. They don't establish currency conversion, tax treatment elsewhere, coupon validity or production integration.
I would ask the agent to identify the source of each important expectation. An acceptance criterion, an approved example, a documented contract or a trusted reference can justify it. The current implementation can tell us what happens, but not always what should happen.
If the requirement doesn't settle whether the threshold applies before or after discounts, generating an assertion doesn't settle it either. The correct next step is a question. An executable guess is still a guess.
A second agent needs a different starting point
It is tempting to solve this by assigning test generation to another agent. That can help, but a separate role is not automatically an independent basis for judgment.
If the second agent receives the implementation and a persuasive explanation of why it is correct, it may simply encode that explanation as tests. Changing the model doesn't guarantee a different interpretation of an ambiguous requirement.
For consequential behavior, I would give the test reviewer the original requirement and ask it to derive concrete outcomes before showing it the implementation. Then compare the cases with the code and existing tests.
This reduces one source of anchoring. It doesn't prove independence or eliminate shared mistakes. Both reviewers may still misunderstand the requirement, which is why the expected outcomes need to remain inspectable by a person.
In Green Checks Only Cover What You Checked, the concern was the reach of evidence across an integration boundary. Here the boundary is earlier: whether the assertion itself expresses the intended behavior. An integration test can have a wrong expectation too.
Check that the test can disagree
A useful test should fail for a relevant wrong implementation. I would deliberately check that property, rather than infer it from a green run.
In this example, ignoring the discount should fail the first requirement-derived case. Changing the threshold comparison from inclusive to exclusive should fail the exact-boundary case.
Mutation testing automates variations of this idea by introducing changes into the code and checking which tests detect them. Tools such as Stryker make that practical without inventing a mutation framework for each project.
A detected mutation establishes sensitivity to that change. It doesn't establish the truth of the original requirement. An incorrect expected value can still kill mutants; a surviving mutant can reflect equivalent behavior rather than a meaningful defect. Mutation scores need interpretation, not ceremonial targets.
Property-based tests add another useful angle. Under our stated policy, increasing the discount while holding the merchandise subtotal fixed must not turn an ineligible order into an eligible one. This describes a relationship across inputs instead of one fixed example.
But a function that always returns false also satisfies that relationship. Properties need enough independent constraints to rule out plausible wrong implementations. They complement concrete examples; they don't manufacture a business rule from nothing.
Fixtures can make the same mistake less visible
I would also inspect how test data is created. A fixture built by calling the implementation under test can quietly recycle the result it is supposed to check. A broad mock can remove the behavior that actually needs verification.
This doesn't make builders or mocks inherently bad. A builder can provide ordinary input data while leaving the important expected result explicit. A mock can isolate a collaborator without deciding the business outcome for the test.
The question is what the helper owns. Does it simplify setup, or does it calculate the answer through the same logic the assertion claims to validate?
Duplicating the production algorithm inside the test is not necessarily a better oracle. It can reproduce the same defect in another file. For a small example, independently calculated values may be easier to audit. For complex behavior, a reviewed reference implementation, contract examples or another representation may be appropriate.
There isn't one universally independent source. The point is to identify the dependence instead of mistaking a second file for a second line of reasoning.
Review the test change, not just its result
When an agent fixes a failing suite, I want to know whether it changed the behavior or changed what the suite accepts.
Sometimes the expected result is genuinely obsolete because the requirement changed. Sometimes the implementation is wrong. Sometimes the test was wrong from the beginning. All three can end with green CI. They are not the same engineering decision.
A snapshot update deserves the same question. Accepting a new snapshot records an output; it doesn't establish that the user should see it. Removing an assertion or relaxing a matcher may be justified, but the justification needs more than getting the run to pass.
For a bug fix, I would prefer the regression test to demonstrate the reported failure on the relevant prior implementation, then pass with the correction. It also needs to fail for the intended reason, not because an unrelated dependency or import is broken.
For new behavior, a failing test against the previous implementation is expected. What matters is that its assertion comes from the requested behavior, not from whichever output the new implementation happened to produce.
Keep generation useful
I like the prospect of an agent handling repetitive setup, exploring input combinations and proposing edge cases. Those are useful contributions even when the final expectation still needs human judgment.
What I don't want is to promote the existence of generated tests into evidence that the requirement has been independently checked. More assertions can make a mistaken assumption look thoroughly documented.
The shipping example is small enough that the disagreement fits in one subtraction. Real systems hide the same question behind fixtures, collaborators and years of existing behavior. I would still start by asking who decided the answer and what supports it.
If you use agents to write tests, how do you keep expected outcomes independent of the implementation? I'd be interested in the checks that expose shared assumptions, especially when both the code and its tests look completely reasonable.
About the author
Maksym Kuzmitskyi is a Senior Software Developer focused on React and TypeScript. His work includes enterprise applications, shared frontend components, Node.js integration, testing and accessibility. Alongside frontend work, he designs AI-assisted delivery workflows and has developed personal review tooling.
More than a blog post
I share frontend news and the reasoning behind it throughout the day. Pick the language that feels natural to you.