
AI-Generated Tests: Can You Trust Them? How to Validate
AI-generated tests can pass and still protect nothing. Learn to validate them with mutation testing, deliberate failures and coverage that checks behavior.
Want to apply this in your company?
Book a call with Juan →AI-Generated Tests: Can You Trust Them? How to Validate
You can trust AI-generated tests only if you have proof that they fail when the code is wrong. Watching them pass is not enough. The most useful techniques are mutation testing, deliberate failure (breaking the code on purpose and seeing whether any test complains), and reading coverage by what is verified rather than by which lines ran. A test that only confirms what the code already does gives a feeling of safety and protects nothing.
Key takeaways
- A passing test proves nothing. A test that fails when the behavior is wrong is what proves something.
- When AI writes a test by reading the code, it tends to describe what the code does, not what it should do.
- Mutation testing and deliberate failure are the two most direct ways to measure whether your tests detect real defects.
- Line coverage shows what was executed, not what was verified. Look at assertions, edge cases and business rules.
- Ask for tests based on the specification, review a sample, and treat the batch as a pilot with a decision criterion.
Why AI-generated tests are so easy to trust by mistake
A language model receives a function and produces tests that compile, run and pass. That is exactly the problem: passing is the path of least resistance. If the test was derived from the code, the expected value is often copied from the result the code itself returns. If the code has a bug, the test inherits the bug and stamps it as correct.
Several patterns show up often in tests generated without care:
- Weak assertions, such as checking only that the result is not null or that no exception was thrown.
- Tests that reimplement the function's logic inside the test itself, repeating the same calculation.
- Heavy use of mocks, to the point where the test only verifies that the mock was called.
- Plenty of happy-path cases and almost no edge cases, invalid input or integration errors.
- Nicely written names that promise a business rule the assertion does not check.
None of this is unique to AI, and people write tests like this too. The difference is volume: AI produces dozens of tests in minutes, and human review tends to relax when everything looks so polished.
A test is worth only what its failure can reveal.
How mutation testing shows whether your tests catch defects
Mutation testing flips the question. Instead of asking whether the tests pass on the current code, it automatically alters the code to create small variations called mutants: it changes a greater-than into greater-than-or-equal, inverts a condition, removes a call, changes a constant. Then it runs the suite against each mutant. If a test fails, the mutant is killed. If every test passes, the mutant survived, which means that behavior is not protected.
Mature tools exist for the languages most common on engineering teams: Stryker for JavaScript and TypeScript, PIT for Java, mutmut for Python and Infection for PHP, among others. Installation details, mutation operators and configuration options are in each tool's official documentation, and it is worth reading before you set any internal rule.
How to use the results in practice:
- Run mutation testing first on your most critical module, not on the whole repository. It is slower than a regular test run.
- List the surviving mutants and read them one by one. Some are equivalent (the change does not alter behavior) and can be ignored.
- For each relevant survivor, write or request a test that kills it, and state in the specification which rule it protects.
- Keep the report with the review, so the decision to accept the batch rests on evidence.
Mutation testing does not replace judgment. It points to where tests are loose, but it cannot tell you whether the business rule being tested is right. For that, you need the specification.
Deliberate failure: the manual method any team can use today
If a mutation tool is not in your pipeline yet, deliberate failure applies the same reasoning by hand. The reviewer picks an important piece of code, introduces a realistic defect and runs the tests. If everything stays green, the test does not protect that piece.
Deliberate defects that tend to expose weaknesses:
- Changing a boundary comparison (greater-than to greater-than-or-equal) in an approval or discount rule.
- Inverting a permission check, for example who is allowed to cancel an order.
- Removing the validation of a required field.
- Returning a value without rounding, or rounding in the wrong direction.
- Ignoring an error from an external integration and carrying on as if it had succeeded.
A simple team rule works well here: no batch of AI-generated tests is merged until the reviewer has broken at least a few of the highest-risk points on purpose and seen the tests fail. It is cheap, needs no tooling and teaches the team to read tests with healthy suspicion.
Another useful variation is the reverse path: ask the AI to write tests without seeing the implementation, only the specification, and run them against the code afterward. When a test fails, there are two hypotheses: the code is wrong or the specification was ambiguous. Both findings have value.
Useful coverage: what to measure beyond executed lines
Line coverage tells you the code ran during the tests. It does not tell you that anyone checked the result. A test with no assertion can produce high coverage and zero protection. So when you evaluate AI-generated tests, change the question from how much is covered to what is verified.
A decision guide in list form, to apply to each test in the batch:
- Is there an assertion on the business result (value, state, message), and not just on the absence of an error? If not, reject or rewrite.
- Does the expected value come from the specification, or was it copied from the code's output? If copied, confirm it with someone who knows the rule.
- Are there edge cases: empty input, zero, a value exactly at the limit, text with accents, a date at the month boundary? If not, ask for them.
- Is there at least one invalid-input case or dependency-failure case? If not, the test covers only the happy path.
- Does the mock replace only what is external, or does it hide logic that should be tested? If it hides logic, simplify.
- Would the test fail if the rule were changed? Answer with deliberate failure or mutation, not with opinion.
Coverage is still a useful signal, but as a floor, not a target. When the goal is a number, both AI and people learn to hit it with weak tests.
Illustrative scenario: the discount rule that passed everything
Illustrative scenario: a distribution company has a function that calculates order discounts. The rule written by the sales team says the discount applies to orders from a certain value upward. A developer asks the AI to generate tests by reading the existing code, and the code, by oversight, applies the discount only to orders above that value, leaving out an order exactly at the threshold.
The AI generates several tests: a small order with no discount, a large order with a discount, an order from an unregistered customer. All pass. Coverage of the function is high. Nobody tested the order exactly at the threshold, and if someone had tested it by reading the code, the expected value would have been copied from the wrong behavior.
During review, the reviewer flips the comparison operator as a deliberate failure and runs the suite. Everything stays green. That is the signal: the boundary is not protected. When a new test is requested from the text of the sales rule, the boundary test fails against the current code, and the real defect surfaces before it reaches a customer.
The example is invented to illustrate the mechanism, but the pattern is common: bugs live at the boundary, and the boundary is where tests generated from code tend to be silent.
Step by step: validating a batch of AI-generated tests
A lean routine that any repository can adopt, in order:
- First, write or gather the specification of the behavior: business rules, limits, expected errors. One paragraph per function is enough.
- Ask the AI for tests based on the specification, and make clear that it should point out when the implementation diverges instead of adjusting the test to pass.
- Run the suite and read every failure. An initial failure is not a problem: it may be the first defect found.
- Review the assertions using the checklist from the previous section and reject tests that only check for the absence of errors.
- Apply deliberate failures at the highest-risk points, or run the mutation tool on the module.
- For each relevant surviving mutant, add a test that kills it and record the rule it protects.
- Make a separate commit for the tests, with a note on how they were generated and validated.
- Run the suite again in continuous integration, so the protection does not depend on one person's machine.
If the same test-generation prompts keep recurring on your team, turn them into a reusable instruction. The article on Claude Code skills shows how to standardize this kind of procedure, and the guide to adopting Claude Code in teams covers limits and review in daily use.
If the system under test includes an AI agent with variable responses, the reasoning is similar but the instrument is different: evaluation sets and regression checks. The piece on prompt regression testing in CI covers that case, and the evaluation documentation from OpenAI and Anthropic explains the logic of measuring behavior against criteria defined in advance.
How to apply this with control on your team
The method we use in AI projects also works for generated tests: map, limit, pilot and hand over. Mapping means identifying which modules hold sensitive business rules and what the risk of a silent defect is there. Limiting means deciding where AI may generate tests on its own (utility code, formatting) and where it only produces a draft for review (calculations, permissions, personal data under Brazil's LGPD).
Piloting means choosing one module, generating the tests, applying mutation or deliberate failure, and recording what happened: how many tests were accepted unchanged, how many were rewritten and which real defects appeared. Agree on the success criterion beforehand, and use the guide on how to measure an AI automation pilot to define metrics and decision gates. Handing over means documenting the procedure so the team can run it without depending on whoever built it, and then deciding whether to expand, adjust or stop.
One more caution: tests often use sample data. Do not paste real customer data into prompts or fixtures. Use synthetic data and follow your internal AI usage policy.
Keep three things separate in your records: what comes from the tools' official documentation, what is established engineering practice, and what is a hypothesis to test on your own codebase. There is no guarantee that a technique works the same way in every language or project, which is why the pilot exists.
Frequently asked questions
Can you trust AI-generated tests? They can be reliable once they are validated. AI writes tests fast, but it tends to mirror the code's behavior instead of the business rule. Treat the batch as a draft until it has gone through assertion review, deliberate failure or mutation testing.
Is mutation testing worth running on the whole codebase? Usually not at the start, because the technique is slower than a regular test run. Begin with critical modules such as calculations, permissions and integrations, and expand if the results justify the time. Check the documentation of the tool for your language to configure the scope.
Does high test coverage guarantee quality? No. Coverage measures what was executed, not what was checked. Tests with no assertion or a weak one can keep coverage high and protect nothing, so combine coverage with mutation testing, deliberate failure and a review of the assertions.
Should I let the AI fix a test when it fails? Carefully. If the test was written from the specification and it fails, the defect may be in the code, and adjusting the test to pass hides the problem. Ask the AI to explain the divergence, and decide based on the business rule, not on the wish to see everything green.