← all posts
// testing · testing

AI for testing: generating tests that catch bugs, not just pass

Tests have a special place in AI coding: the model can write them, and they're also the oracle that makes everything else the model writes trustworthy. Both matter, and the first one has a trap sharp enough to undermine the second.

The tautology trap

Ask an LLM to "write tests for this function" and it reads the implementation, then writes assertions that mirror what the code currently does. The tests pass. They would even if the code is wrong, because they were derived from the code, not from what it's supposed to do. These tautological tests are worse than no tests: they paint a green checkmark over a bug and call it covered.

A test written from the implementation can only ever confirm the implementation. If the code is wrong, the test is wrong in exactly the same way — and now you trust it.

How to get tests that actually verify

  • Test from the spec, not the code. Give the model the intended behavior (the requirement, the docstring, the ticket) and have it test against that. The gap between "what it does" and "what it should do" is where bugs live, and only a spec-grounded test can see it.
  • Demand edge cases explicitly. Models default to the happy path. Ask for the empty input, the boundary, the overflow, the error path, the concurrent case: the inputs that actually break things.
  • Review the assertions, not the pass/fail. A green AI-generated suite can be entirely tautological. Read what each test asserts and ask "would this fail if the behavior were wrong?" If not, it's theater.
  • Think in invariants. What must always hold regardless of input? Property-style thinking catches whole classes of bugs that example-based tests miss.

Where AI testing genuinely shines

  • Breadth. Generating the many tedious cases, parametrized variations, and scaffolding a human would skip out of boredom.
  • Cases you forgot. "What edge cases am I missing here?" is a great prompt. The model suggests inputs you didn't think of.
  • Coverage on legacy code: keep the spec-not-implementation caveat in mind, since for untested legacy code you often only have the implementation to go on (so review extra hard).

The chicken-and-egg you can't ignore

Here's the bind: good tests are what make agentic coding and the verification gate work. They're the oracle. But if you let the same AI write both the code and its tests, it'll write tautological tests for its own code and "verify" itself into false confidence. So keep a human in the loop on the tests, or generate tests from an independent, spec-grounded process, not from the same pass that wrote the code. Tests are also evals by another name; the discipline is the same.

Maintenance is its own trap

AI is genuinely useful for diagnosing flaky tests and updating tests after intended behavior changes. But "update the test to match the new behavior" is the exact spot where it'll happily paper over a real regression. If the behavior changed by accident, the obedient fix is to bless the bug. Always ask: did this behavior change on purpose? before you let it rewrite the assertion.

The honest take

AI is excellent at the volume of testing: cases, scaffolding, coverage, suggestions. It's weak at the judgment of testing (is this the right behavior to assert?). So own the judgment: write the intent and the critical assertions yourself (or from the spec), and let AI fill in the breadth around them. Do that and AI testing makes your suite both bigger and better; skip it and you get a large, green, meaningless suite that fails you exactly when it matters.

#testing#quality#agents