All articles

Testing AI-Generated Code: 6 Checks Before You Trust a Green Checkmark

A test only counts as evidence when it challenges the code. For your whole career, that independence was built in without anyone having to think about it. Someone wrote the code, and the tests existed to try to break it. A pass meant the code survived an attack.

AI quietly removed that. When one agent writes the code, writes the tests, runs them, and reports the pass, the green checkmark is a single witness vouching for itself. It did not become less trustworthy. It stopped being evidence at all. That is what makes a green build the most convincing lie in your pipeline: it still looks exactly like the proof it used to be.

I learned how far that goes building a mobile game almost entirely through AI coding agents. One billing bug survived more than a dozen confident fixes, and every one of those fixes came with a green checkmark. The problem is not a game problem. It shows up on web apps, APIs, and mobile apps the moment an AI writes code for you. The six checks in this article are how I put independent evidence back into the loop, written so you can run them tomorrow on your own project.

Look at these two screens. Both say finished. Three stars, coins earned, rewards collected.

A level-complete screen showing three stars, a score, unused moves, and 28 coins earnedA treasure reward screen with chests, collected items, and a grand reward counter

Both screens look finished. The real questions are underneath: were those coins actually granted, saved, and still there after the app restarts. An agent reports the screen. A tester checks the system behind it.

Can you trust AI-generated code that passes its tests?

No, not on the pass alone. A passing test only proves the code satisfies that test, and when the same AI wrote both the code and the test, the pass is one witness vouching for itself. Before you trust a green build, verify the claim yourself: read the generated tests for weak assertions, walk the whole user flow rather than the happy path, and confirm the change is in the build that actually ships. The six checks below take you through it in order.

Why the agent says done when it is not

An agent stops when the work looks finished, and “looks finished” is the only signal it has unless you give it a better one. Anthropic’s own Claude Code best practices say it plainly:

“Claude stops when the work looks done. Without a check it can run, ‘looks done’ is the only signal available, and you become the verification loop.”

This matches what developers report everywhere. In the 2025 Stack Overflow developer survey, the most common frustration with AI tools was solutions that are “almost right, but not quite,” and forty-five percent of developers said debugging AI-generated code takes them more time, not less.

Does debugging AI-generated code take more time?Says it takes MORE time45%Does not55%

It can get worse than an agent stopping early. Anthropic’s research on reward hacking found a model that learned to pass coding tests by exiting cleanly before a single assertion ran. The everyday version needs no malice: an assistant rewarded for green takes the cheapest path to green, which can mean weakening an assertion or writing a test that only confirms the code it just produced.

So the job is not to trust the checkmark. It is to know which checkmarks mean something. Here are the checks, in the order I run them.

Check 1: Ask which level of “done” the AI is claiming

When an agent reports success, place the claim on this ladder before you do anything else. The bottom rungs are nearly free. Only the top rung counts before a release.

Levels of “done”It compilesprogressA test passesprogressThe build is greenprogressIt works where real users aregetting warmRoot cause fixed at the owning layer,present in the shipping buildEverything below the top band is progress. Only the top band is done. Agents report the bottom three as “done.”

Write the rule into your project instructions in plain language, so the agent reports against it too:

Local code, a committed branch, a passing test, or a preview-only route is progress, and you report it as pending until the build that ships actually contains it.

That single distinction, progress versus done, catches more false green checkmarks than anything else in this article.

Check 2: Run the narrowest test that proves the AI’s change

A thousand-test green wall is hard to read and easy to trust. Before you accept the big suite, run the one test that exercises the exact contract the AI changed. It is faster, and it is far more honest, because you can actually read its output.

  • Changed a checkout flow? Run the checkout tests alone, and run them more than once: npx playwright test tests/checkout.spec.ts --repeat-each=10. A test that passes nine times out of ten is a failing test with good timing.
  • Changed an API endpoint? Call the endpoint directly and read the response body, not just the status code.
  • Then read the run output, not just the exit code. Test runs can exit green while a real failure sits in the error stream above the summary. Scan the output for the failure words on your stack (Parse Error, Failed to load, Unhandled, whatever “quiet failure” looks like in your logs) before you call anything a pass.

Check 3: Read the AI-generated test’s assertions, not just the pass

Even a test that runs and passes can prove almost nothing. The most common problem with a generated test is not that it is broken, it is that it is weak. Ask an assistant to “add a test that the purchase works” and you get this:

Me → assistant
Add a test that the purchase works.
Assistant
Done. Added a test that buys the Pro plan and checks the thank-you message appears. It passes.
Playwright
test('verify that a user can purchase the pro plan', async ({ page }) => {
  await page.goto('/pricing')
  await page.getByRole('button', { name: 'Buy Pro' }).click()
  await page.getByLabel('Card number').fill('4242424242424242')
  await page.getByRole('button', { name: 'Pay' }).click()
  await expect(page.getByText('Thank you for your purchase')).toBeVisible()
})

Green, and it looks responsible. But the promise of a purchase is not “a thank-you message appears.” It is “this account now has the Pro entitlement, and it is still there next login.” That test passes even if the payment succeeded and the entitlement never got written, which is the exact failure that becomes a refund. The stronger version checks the real state and that it survives the round trip:

Playwright
test('verify that buying the pro plan grants the pro entitlement', async ({ page, request }) => {
  await page.goto('/pricing')
  await page.getByRole('button', { name: 'Buy Pro' }).click()
  await page.getByLabel('Card number').fill('4242424242424242')
  await page.getByRole('button', { name: 'Pay' }).click()
  await expect(page.getByText('Thank you for your purchase')).toBeVisible()

  const account = await request.get('/api/account/entitlements')
  expect(await account.json()).toMatchObject({ plan: 'pro', status: 'active' })
  await page.reload()
  await expect(page.getByRole('link', { name: 'Pro dashboard' })).toBeVisible()
})

Hold one question over every generated test: would this fail if the product were broken in the way users care about? If the answer is no, the green tick is decoration.

Check what the system actually did, not just what the screen showed.

Name each test after the promise it defends, like verify that buying the pro plan grants the pro entitlement, and a hollow test gives itself away the moment the name claims more than the body checks. Reviewing a whole batch of generated tests is its own discipline, and I walk through it gate by gate in how to review AI-generated automated tests.

Check 4: See it work where your real users are

An agent reading the code will tell you a flow works. The running product tells you the truth, and they disagree more often than you would like. This is where the division of labour matters, because AI is genuinely uneven here:

  • Hand the agent the evidence work. Capture logs while you reproduce the problem by hand, then give the agent the whole log file. Handed thousands of lines, an agent is excellent at finding where the backend stopped returning what it should. That analysis is real AI strength.
  • Keep the eyes-on-the-product work. AI still cannot reliably see that a UI is visually wrong, that a button is clipped, or that a flow feels broken. On mobile and on anything with many screen sizes, this gets worse, not better. You have to open the real app, on the real device or browser your users have, and walk the flow yourself.
  • Walk the whole chain, not the happy path. A purchase is not “the button worked.” It is buy, then grant, then persist, then acknowledge, then refresh, and a break anywhere in that chain looks fine from the button. I wrote up that chain-tracing habit in done is the whole chain.

Check 5: Ask whether the AI’s fix is real or a mitigation

The failure that taught me the most in the game project was a billing problem that went through more than a dozen attempted fixes before the real cause was found. Each earlier fix patched a layer downstream of the break. A retry here, a bit of masking there, and the symptom would quiet down enough to look solved. It was not solved.

So make this a standing question you ask the agent, and yourself, on every fix:

Have we fixed the cause at the layer that owns it, or have we hidden the symptom?

Anything that is a workaround, a retry, or a bit of interface masking gets labelled as mitigation, never as done, until the real owner is found and fixed. And if a bug survived an earlier fix, assume that earlier fix hit the wrong layer until the live path proves otherwise.

Check 6: Verify the build that ships, not just the source code

Your code can be perfect and the thing you deploy can still be broken, because the build is a separate artifact with its own failure modes. A dependency pinned differently in CI, a stale config on the build machine, a test-only library leaking into the production bundle. The tests were green. The tests can also be the bug.

So after the build, inspect the artifact as its own object:

  • Confirm the shipped artifact contains your change. A feature that exists in the repository but not in the deployed build is progress, not done.
  • Confirm nothing extra got in. Development and test dependencies do not belong in a production bundle, and app stores will reject builds that carry them.
  • Confirm the signature and record a checksum if you ship signed packages, so you always know exactly which build a bug report came from.

One more thing belongs in this step: separate your blockers from external ones. A failing test is yours, and an agent can help clear it. A pending store review or an unapproved account setting is not, and no amount of agent work will clear it. Track the external ones as real, named tasks, so a repository full of green checkmarks never gets mistaken for a product that is allowed to ship.

The 6 checks for AI-generated code, in one place

When an agent hands you a green checkmark, run it through these six questions before you believe it:

  1. Which level of done is this? Compiled and committed is progress. Working where users are, with the cause fixed, is done.
  2. Did the narrowest test pass, and did I read the run output? Not just the big suite, and not just the exit code.
  3. Would the test fail if the product broke in the way users care about? A weak assertion that only checks “something appeared” is decoration.
  4. Did I see it work where my users are, across the whole flow? The real device and the real browser outrank the agent’s confidence.
  5. Is this a fix or a mitigation? A masked symptom is not a fixed cause.
  6. Did I verify the artifact that ships, not only the source? The build is its own object.

Here is the compact version to copy into your notes, your pull request template, or your team channel:

Before trusting an AI "done" claim:
1. Which level of done is this? Committed = progress. Working for users, cause fixed = done.
2. Did the narrowest test pass, and did I read the run output, not just the exit code?
3. Would the test fail if the product broke in a way users care about?
4. Did I see it work on the real device or browser, across the whole flow?
5. Is this a fix or a mitigation? A masked symptom is not a fixed cause.
6. Did I verify the artifact that ships, not only the source?

None of this is exotic. It is what testers have always done, pointed at a new source of confident, fast, plausible output. Strip the six checks down and they are all one move: bringing in a witness that is not the code vouching for itself. A narrower test the agent did not choose, an assertion you read with your own eyes, a real device, a shipped build. The agents will keep getting faster and more convincing, which makes that independence more valuable, not less. A green build will always look like good news. Your job is to be the witness it has to convince. That is QA acting as the control layer, one green checkmark at a time.

Next time an agent says “done,” do not argue and do not trust it. Pick the one check that would actually prove the feature for a real user, run that, and watch how often “done” turns into “almost.” That gap is where your value lives.

Found it useful? Share it.
Julia Pottinger

Written by

Julia Pottinger

Hi, I'm Julia. I've been in QA for over a decade. I spend my days testing software and my own time building apps and games, and I write here to share what I learn, the practical, honest lessons you can actually use.

Comments 0

Share your thoughts, ask questions, or add to the conversation.

Be kind and constructive. Stay on topic. No spam or self-promotion.
Loading comments…