AI-generated tests: coverage is not confidence
Coverage answers whether the line ran, not whether anyone checked the result. The right question is whether any test fails when you break the behaviour.
Asking a model for tests is probably the best-received AI use in any team: nobody enjoys writing tests for legacy code, coverage climbs fast, and the chart looks great. It is also where the illusion of quality settles in most easily.
Coverage measures execution, not verification
Coverage answers exactly one question: did this line run during the suite? It does not answer whether anyone checked the result.
It is trivial to write a test that executes a hundred lines and asserts almost nothing. If a model generates a hundred tests like that, you get 90% coverage and a suite that stays green after a change that broke the behaviour.
The useful question is not "how much of the code was covered?", it is "if I break this behaviour on purpose, does any test fail?". Those two questions have very different answers, and only the second one tells you whether the suite is doing its job.
A test generated from the code inherits the bug
This is the structural problem, and no better prompt solves it.
When you hand the implementation to the model and ask for tests, it describes what the code does. If the business rule is implemented wrong, the generated test now protects the error. Worse: from then on, fixing the bug breaks the test, and somebody will "fix the test".
The alternative is generating tests from the specification, the ticket, the written rule, and only then running them against the implementation. When the two disagree, you learned something. That is the spirit of TDD, with the order preserved.
Where automated generation genuinely helps
Three cases with clear returns:
- Legacy code with no coverage at all. Here even a characterisation test, which only freezes current behaviour, has value: it is a safety net before refactoring.
- Edge cases nobody remembers. Asking for the list of boundary inputs (empty, null, negative, unicode, numeric limits) is where the model earns its keep.
- Coverage plateaus. Research presented at ICSE 2023 showed that using an LLM to unstick search-based test generation helps exactly when the automated technique stalls, which is the point where it usually stops on its own.
Watch for flakiness
A large volume of generated tests raises the risk of flaky tests, and flaky tests cost more than they appear to. Google's engineering has published that around 1.5% of their tests are flaky, and that they consume time and confidence disproportionately.
The practical effect is well known: once a red build becomes noise, the team stops looking. A suite with many unstable tests is worse than a smaller, reliable one.
Simple rule: a test that fails intermittently leaves the blocking gate the same day, becomes a fix item, and only returns once it is deterministic.
Test size matters more than test count
Google's testing practice organises suites by size rather than by theoretical layer: small tests run in a single process with no network or disk; medium ones may use localhost; large ones may use external resources. The goal is predictability and speed.
When AI generates tests at volume, it tends to produce medium and large tests disguised as unit tests, because the model does not know which dependencies you consider expensive. Check that explicitly: if your unit suite got slow after adoption, that is why.
How to check whether the suite has teeth
A cheap and revealing exercise: take three important business rules, break each one on purpose in the code, and run the suite. If nothing turns red, the coverage is protecting nothing.
This is a manual, impoverished version of mutation testing, and it is enough to decide where to invest. If you want to go further, mutation tools automate the idea, but start with the manual exercise: it costs an hour and usually changes the conversation about coverage targets.
What this means for your team
Stop using coverage percentage as a target and start using it only as an alert when it drops. Adopt the three-broken-rules exercise once a quarter.
When generating tests with AI, generate from the written rule rather than from the code whenever the rule exists. And when a test characterises legacy behaviour, mark that in the filename: whoever comes next needs to know that test describes current behaviour, not desired behaviour.
References
The sources behind this article, so you can check them and dig deeper.
- 1Software Engineering at Google: testing chaptersWinters, Manshreck, Wright (O'Reilly / abseil.io), 2020
- 2Flaky Tests at Google and How We Mitigate ThemGoogle Testing Blog, 2016
- 3CodaMosa: Escaping Coverage Plateaus in Test Generation with Pre-trained Large Language ModelsLemieux, Inala, Lahiri, Sen (ICSE), 2023
- 4Technology RadarThoughtworks
Read next
AI code review without lowering the quality bar
People using AI assistants write less secure code and feel more confident it is secure. The gate needs to get stricter, not looser.
Read the articleThe new technical debt: code you did not write
The old debt had someone who knew why they took the shortcut. The new one grows silently, and you discover it during an incident.
Read the articleModernising legacy systems with AI as an archaeologist
That odd condition in the shipping calculation handles a real case nobody documented. Finding that out is the dominant cost of the project.
Read the article