The Confirmation Bias Problem in Manual Testing
When an engineer writes a function and then writes tests for it, both activities share the same mental model of how the function should behave. The tests confirm what the engineer already believes — which is precisely why they miss the cases that actually cause production bugs.
This isn't a skill problem. It's a structural one. The person who knows the code
best is the worst person to write adversarial tests for it. They'll test
parseAmount("100") and parseAmount("100.50"), but not
parseAmount(""), parseAmount(null),
parseAmount("1e308"), or parseAmount(" 100 ") with
leading and trailing whitespace.
The engineer who wrote the parser knows it expects a string. So they test strings. AI has no such assumption — it generates the full space of what a string can be.
In our experience, a large share of production bugs could have been caught by tests that were theoretically possible to write but were never written. Not because engineers are careless, but because the mental model that lets you build something also blinds you to how it can break.
What AI Generates Differently
When we feed a function to an AI model and ask for a test suite, it doesn't start from intent — it starts from the function's type signature, control flow, and any documented contracts. The output typically covers four categories that human-written tests consistently underweight:
1. Boundary values
Off-by-one errors are the oldest bug in software. AI-generated tests reliably
hit n - 1, n, and n + 1 for every
numerical boundary in the function, including ones the engineer didn't think
of as boundaries because they're implicit in a downstream condition.
2. Type edge cases
In dynamically typed languages especially, functions receive inputs the author
never intended. undefined instead of null,
0 instead of an empty array, a single object instead of an
array of one. AI-generated tests enumerate these systematically.
3. Concurrency and ordering assumptions
For async code, AI will generate tests that verify the function handles resolved and rejected promises in different orders — something engineers rarely write explicitly because the happy path always resolves first in their mental model.
4. State mutation side effects
When a function modifies its input rather than returning a new value, AI-generated tests frequently catch that the input object is mutated in ways the caller doesn't expect — a common source of subtle bugs in shared state systems.
A Concrete Example
Here's a simplified version of a payment amount validator we worked with on a recent FinTech engagement. The human-written test suite had 6 tests, all of which passed. The AI-generated suite had 24 tests — 4 of which failed, revealing bugs that had been dormant in a function running in production for eight months.
// The function under test
function parseTransactionAmount(input: string): number {
const cleaned = input.replace(/,/g, '').trim();
const amount = parseFloat(cleaned);
if (isNaN(amount) || amount <= 0) {
throw new Error('Invalid amount');
}
return Math.round(amount * 100); // store as cents
}
The 4 failing AI-generated tests exposed:
- Scientific notation:
"1e2"returns10000(100 dollars in cents), not an error. Whether that's correct depends on the product — but it was never a deliberate choice. - Negative zero:
"-0"passes theamount <= 0check because-0 <= 0is true in JavaScript, butisNaN(-0)is false. It returns-0in cents, which fails downstream integer assertions. - Very large values:
"9999999999999999"returns a different value than expected due to floating-point precision loss before theMath.roundcall. - Whitespace-only input:
" "passes the trim and replace, becomes an empty string, parses toNaN, and throws correctly — but the error message was missing required context for the upstream error handler.
None of these were edge cases an engineer would naturally think to test after writing this function. All four were caught by AI test generation in under 90 seconds.
Where AI Still Needs Human Review
AI-assisted testing is not a replacement for engineering judgment — it's a way to extend it. There are three areas where the generated tests require careful human review before they go into the suite:
- Tests that assert wrong behaviour. AI infers expected outputs from the code itself, not the intent. If the code is wrong, some generated tests will assert that the wrong behaviour is correct. Every test that defines an expected value needs a human to verify that value is actually right.
- Integration test setup. Unit tests for pure functions are excellent. Tests that require database state, external APIs, or message queues need human scaffolding — the AI generates the assertions but not the infrastructure setup.
- Business rule edge cases. AI doesn't know that in your domain, a zero-dollar transaction is valid (for free trials) even though the validator above would reject it. Domain knowledge still has to come from the engineer.
How to Use AI for Unit Testing: Our Workflow
On every engagement, we use a two-pass approach to test generation:
- AI first pass: Run Claude or Copilot against each new function or module, generate a candidate test suite, and commit it to a review branch.
- Engineer review: A senior engineer reads each generated test, verifies expected values against the spec, removes tests that assert incorrect behaviour, and adds any domain-specific cases the AI missed.
- Coverage gap analysis: Run the combined (AI + manual) suite through a coverage report. AI-generated tests usually reach high branch coverage before the human review, and the review closes the remaining gaps.
The result: test suites that would take days to write by hand are reviewed
and production-ready within hours. The engineers spend their time on judgment,
not on typing expect(fn(null)).toThrow() for the fourteenth time.
Bottom Line
AI-assisted testing isn't about replacing engineers — it's about redirecting their attention. The mechanical work of enumerating inputs belongs to AI. The work of knowing what the correct output should be, understanding the domain, and deciding which edge cases are actually worth testing in your context — that belongs to the engineer.
Used correctly, it's one of the highest-leverage AI applications in the engineering workflow. Every hour spent reviewing AI-generated tests pays back in production bugs that never happen.