Why Quality Assurance Matters More Than Ever in the Age of AI-Generated Code
← Back to Blog
September 2026·AI Engineering·11 min read

Why Quality Assurance Matters More Than Ever in the Age of AI-Generated Code

Quality assurance for AI-generated code needs human judgment. See my three-stage workflow for review, manual testing, and safer delivery.

The urgent message came just after a match. A production issue was affecting a customer, and someone had to get back to a laptop.

That is the part of software quality nobody puts in an AI demo. The model produces a clean diff in seconds. The presentation ends there. Real engineering starts when that diff meets old data, ambiguous requirements, browser state, permissions, integrations, and people trying to do their jobs.

Quality assurance for AI-generated code matters more than ever because code can now be produced faster than teams can understand and verify it. AI is not the problem by itself. The dangerous combination is speed without skepticism, output without ownership, and a pull request that looks finished before anyone has proved that the feature actually works.

I use AI every day. I have built my development process around it. But the more capable the tools become, the less willing I am to confuse generated code with completed work.

AI did not invent bad code. It changed the volume.

Developers shipped bugs before large language models existed. We misunderstood tickets, missed edge cases, skipped local checks, and broke unrelated features. AI did not create carelessness.

What it changed is the amount of plausible code we can create before our judgment catches up.

A model can generate a controller, service, migration, frontend state, and tests in the time it once took to think carefully about one boundary. The output often looks convincing. Names are clean. The happy path works. The test suite may even be green, especially when the same model wrote both the implementation and the tests from the same incomplete understanding.

That creates what I think of as verification debt: the growing distance between how much code exists and how much of its behavior a human has actually challenged.

Technical debt usually becomes visible later. Verification debt can reach production immediately.

Three incidents that changed how I think about quality

I am deliberately removing names, dates, products, and identifying details from these examples. The point is not to blame individual developers. I have shipped bugs too. The point is to show a pattern I now see more clearly in AI-assisted teams.

A small uploader change stopped real work

In one case, a developer updated an uploader and did not test the complete flow carefully enough. The change looked routine. In production, customers could not upload what they needed for an entire working day.

The important failure was not that a developer wrote imperfect code. That is normal. The failure was that the task was treated as complete before somebody used the feature the way a customer would: choose a file, submit it, observe the response, and confirm the result persisted correctly.

A few minutes of realistic verification could have protected hours of customer frustration.

AI-generated changes reached UAT without a local run

Another change was produced with AI and pushed without being tested locally. Fortunately, the problem was caught during user acceptance testing rather than by customers.

That was lucky, but it also exposed a process gap. The code existing in a branch had been mistaken for the task being done.

The incident helped drive a dedicated UAT meeting where we verify that delivered behavior matches what stakeholders expected. That meeting is useful. It creates a shared moment for validating intent, not only implementation.

But UAT should be the final confirmation, not the first time a feature meets reality.

The same kind of filter broke twice

Then AI-assisted changes broke filters in a CRUD interface for the second time in a month. Filters are exactly the kind of feature that can look trivial in a diff and still fail through combinations: empty values, multiple filters, clearing state, pagination, old URLs, or a mismatch between frontend parameters and backend queries.

The second occurrence mattered more than the first. A repeated class of regression is no longer just a coding mistake. It is evidence that the feedback from the first failure did not become a durable check in the process.

These incidents did not make me anti-AI. They made me more demanding about the work that must happen after generation.

UAT is valuable, but it is not developer testing

User acceptance testing answers a critical question: does the delivered feature behave the way stakeholders expected? Local and manual developer testing answer a different one: does the implementation survive realistic use, edge cases, and integration boundaries?

We need both.

A stakeholder should not have to discover that a basic upload button does nothing. A UAT meeting should focus on whether the workflow solves the right business problem, whether terminology makes sense, and whether the result supports real work. It should not become an expensive substitute for opening the application locally.

Automated tests are another layer, not an alternative. They protect known contracts and regressions. Manual exploratory testing looks for the thing nobody encoded as an assertion because nobody thought to ask.

The strongest process uses each layer for what it does best:

  • Automated tests protect repeatable technical contracts.
  • Developer testing challenges the running implementation.
  • UAT validates the stakeholder's intended outcome.
  • Production monitoring catches the remaining unknowns quickly.

My three-stage workflow for AI-assisted development

My process is not designed to make AI look intelligent. It is designed to make the final change defensible.

1. Let AI produce the rough first pass

I begin with a task number. My tooling retrieves the task automatically from the customer's board, so the model receives the available description and context without me copying it manually between systems.

Then AI generates the initial implementation.

I am comfortable calling this first output AI slop. That does not mean it is useless. It means it has not earned trust. It is raw material: fast, sometimes impressive, often helpful, and allowed to be wrong.

Treating the first answer as disposable changes the psychology of the whole task. I do not become attached to code because it arrived quickly. I can delete it, narrow it, or reject the approach completely.

2. Enter a critical review loop

Next comes the part that requires actual engineering judgment.

I work with planning and code-review tools open. I inspect the diff, compare it with existing patterns, challenge assumptions, and send the model back with corrections. We move back and forth until the implementation matches both the requested task and the quality bar I am willing to own.

I ask questions such as:

  • Did the model solve the requirement or only the example in the ticket?
  • What existing behavior changed unintentionally?
  • Does this reuse the codebase's established pattern?
  • Are validation, permissions, and failure states preserved?
  • Did it add abstractions because they are useful or because generated code tends to expand?
  • Could I explain and debug this change without AI beside me?

This is the difference between being AI-assisted rather than AI-dependent. The model can generate and revise at machine speed. I remain responsible for direction, trade-offs, and the decision to stop iterating.

Code review is not a ceremony where I admire a clean diff. It is an attempt to prove the diff wrong before production does.

3. Test the running application manually

Only after I like the code do I move into manual testing.

I use a custom skill that opens the application, follows a test plan, exercises the interface, captures evidence, and helps search for cases that neither the task description nor the implementation made obvious. The screenshot below shows a privacy-safe demonstration against gawrys.me with a fictional task, DEMO-204. It contains no customer application, data, or credentials.

Quality assurance for AI-generated code using a manual-testing skill on gawrys.me
A real run of my manual-testing skill against gawrys.me using a fictional task and placeholder data.

This stage matters because users do not experience classes and functions. They experience screens, delays, error messages, state changes, disabled buttons, stale filters, and files that either upload or do not.

I test the happy path, but I do not stop there. I clear fields. Repeat actions. Use unexpected but valid input. Refresh at inconvenient moments. Combine filters. Move backward. Check whether visible success matches persisted state. Look for the path that was obvious only after seeing the feature run.

I described the deeper testing mindset in how I test AI-generated code. The short version is simple: I want tests that challenge the implementation, not tests that politely agree with it.

What the manual-testing skill actually adds

Automation is good at repeating what we already know to check. Exploratory testing is valuable because the most expensive defect may sit outside that list.

My manual-testing skill gives the session structure and evidence. It makes the plan visible, records what was exercised, and keeps the running product in view while I reason about behavior. It does not replace judgment. It creates a better environment for judgment.

That distinction is important. Adding another AI agent does not automatically solve the risk created by AI-generated code. If the implementation agent and the testing agent share the same assumptions, they can confidently miss the same requirement together.

The human still has to ask the awkward question:

What would a real user try that this task, this diff, and these generated tests have all failed to imagine?

Sometimes the answer is a strange edge case. Sometimes it is the most basic action on the page.

Ownership is the real quality gate

The best quality rule I know is not about a particular testing framework. It is this: do not ship a change you cannot personally defend.

If production breaks tonight, can I explain the intended behavior? Can I locate the likely boundary? Do I know what evidence justified the merge? Could I continue debugging if the AI tool disappeared?

If not, I do not own the change yet.

Ownership does not mean writing every line by hand. I use AI aggressively because it saves time and expands what I can build. Ownership means the speed belongs to the tool, while the decision belongs to the engineer.

This is also how we build automation that survives production: clear responsibility, observable outcomes, and human judgment at boundaries where a plausible answer is not enough.

Seven questions before an AI-assisted change ships

Before I consider the work finished, I want clear answers to seven questions:

  1. Does the implementation solve the actual requirement, not only the prompt's example?
  2. Did I run the changed behavior locally in a realistic flow?
  3. Which existing behaviors could this diff affect indirectly?
  4. Did I test failure states, empty states, repeated actions, and combinations?
  5. Are automated tests independent enough to catch the model's misunderstanding?
  6. Does UAT confirm the stakeholder's outcome rather than perform basic developer QA?
  7. Could I explain, maintain, and debug every important decision in this change?

If one answer is vague, the task may be coded, but it is not done.

Faster code needs stronger verification

I do not want to return to a world where every predictable line is typed manually. AI makes me faster, and when used well it gives me more time for architecture, edge cases, and the parts of engineering that require judgment.

But that benefit only exists if we spend some of the time we save on verification.

The alternative is not real speed. It is moving effort from development into UAT, production support, customer frustration, and evenings when an urgent message pulls somebody back to a laptop.

AI-generated code is still our code when it ships. The customer does not care whether a broken uploader was handwritten, copied, or produced from a task number by a model. They care that it does not work.

So my position is not complicated: use AI aggressively, review it critically, test the running product manually, and keep ownership human.

Speed is useful. Confidence has to be earned.
Igor Gawrys
Igor Gawrys
AI Engineer & IT Consultant · Katowice, Poland