DEV Community

Cover image for Techniques for Making AI Agents Your QA Team for Reliable Modernization
Emmanuel R for CobuildX AI

Posted on Originally published at cobuildx.ai

Techniques for Making AI Agents Your QA Team for Reliable Modernization

Agents can port a legacy screen in minutes, so the bottleneck moves to proving the new code behaves like the old. These are the techniques that turn agents into a QA team you can trust: layered gates, characterization tests, maker-checker review and visual diffs.

AI agents can now port a legacy screen in minutes. That moves the bottleneck. The hard question in a legacy system migration is no longer "can we write the new code?" but "how do we know it behaves exactly like the old code?"

Manual QA can't keep up with agent-speed porting, and a passing build proves very little. The answer is to point agents at verification too, and to set them up so they find problems instead of approving them. Here are the techniques that make an agent QA team you can trust.

Why migration testing is different

Normal QA asks whether software meets a spec. Modernization QA asks whether the new system matches the old one, and the old one usually has no spec. Its real behavior is spread across templates, watchers, global events, server quirks and years of bug fixes nobody wrote down.

So the goal is parity: same outputs, same side effects, same edge cases, unless a difference is a deliberate, recorded decision. Every technique below serves that goal.

“Program testing can be used to show the presence of bugs, but never to show their absence.” — Edsger W. Dijkstra, Notes on Structured Programming

Technique 1: Five layers, cheapest first

Five QA layers from cheapest to most expensive: static gates, characterization tests, behavior parity review, visual and end-to-end diff, and human sign-off on a risk-ranked sample

Don't send every change to the most expensive check. Stack the gates so cheap, deterministic checks catch the obvious problems before an agent or a person spends time on them:

  1. Static gates. Types, lint, build, and a banned-API list (no jQuery in the React tree, no direct DOM access, no old global stores).
  2. Characterization tests. Tests that record what the old code does today, written before the port.
  3. Behavior parity review. A separate agent compares old and new logic and reports differences.
  4. Visual and end-to-end diff. Screenshots, user flows and network calls, old against new.
  5. Human sign-off. People review a risk-ranked sample, not every line.

Nothing merges on the porter's word. Each layer is a gate, and a failure sends the unit back.

Bar chart of the modeled share of behavior bugs reaching production as layers stack: 100% with no gates, 70% after static gates, 28% after characterization tests, 14% after the parity review agent, 8.4% after visual and E2E diff, 5.9% after a human risk sample

No single layer is close to perfect, and none needs to be. With modest catch rates for each layer, the model above lets through about 6% of behavior bugs, against 28% when you stop at tests. The layers only multiply like this when they look at different things, which is why each one has a distinct job.

Technique 2: Write characterization tests before the port

Have a test-writer agent read the old unit and write tests that pin down its current behavior: rendered output for known inputs, events fired, API calls made, validation messages shown, what happens on empty, error and slow responses. These tests run against the old code first and must pass there.

Then the same tests run against the new code. Because they were written from the original, not from the port, they can't inherit the porter's misunderstandings.

Lock the tests once they pass on the old code. If a porter edits a characterization test to make it pass, that is a finding, not a fix.

“To me, legacy code is simply code without tests.” — Michael Feathers, Working Effectively with Legacy Code

Technique 3: Maker and checker never share a context

The porter agent writes new code and sends the diff and test results to a reviewer agent that sees only old code, new code and tests. The reviewer returns findings with file, line and scenario, looping until there are no confirmed findings or a human breaks the tie

An agent reviewing its own work mostly agrees with itself. It already has a story for why every line is right. The reviewer has to be a separate agent with a fresh context that sees only the evidence: the original code, the new code and the test output.

Give the reviewer a narrow job and a strict output format. Ask for concrete findings, each with a file, a line and a failing scenario, and nothing else:

Compare src/legacy/orders/OrderList.js with src/app/orders/OrderList.tsx.
Report only behavior differences a user or API consumer could observe.
For each: file:line, the input or scenario, old result, new result.
Do not comment on style. Do not suggest refactors.
If you find none, say so.
Enter fullscreen mode Exit fullscreen mode

Run a second pass on the findings themselves. A verifier agent tries to reproduce each one, and only confirmed findings go back to the porter. This keeps false alarms from wasting porter time and teaching the team to ignore the reviewer.

The same logic applies to choosing models for each layer. Use a different model family for the reviewer than for the code it checks, put cheap fast models on high-volume checks, and keep top-tier models for the judgment calls.

Bar chart of an example setup, agent calls per migrated unit by QA layer: static gates use no model, characterization tests 1 call on Sonnet 5.5, behavior parity review 1.3 calls on GPT-6 Astra, visual and E2E diff 2 calls on Sonnet 5.5, finding verifier 1.5 calls on Haiku 5.5, human sign-off prep 0.2 calls on Opus 5.5

Technique 4: Diff what the user sees and what the server sees

Logic review misses things a user notices immediately: a shifted layout, a missing loading spinner, a date in the wrong format. An agent driving a browser can run the same flow on the old and new app, take screenshots at each step and compare them.

Record network traffic too. If the old screen made three API calls with certain parameters and the new one makes five, or drops a header, something changed. Network diffs catch problems that never show up on screen until production traffic hits them.

Technique 5: Spend human time where risk is highest

Agents make human review cheaper, not unnecessary. Have the orchestrator rank units by risk: payments, auth, permissions, data writes and anything with a history of incidents go first. People review those in full, and a sample of the rest.

Grouped bar chart of modeled human review hours for 200 units. Reviewing every unit takes 400 hours. Risk-ranked review takes 124 hours: 80 for all high-risk units, 24 for a 20% sample of medium, 20 for a 10% sample of low

In this model, risk ranking cuts senior review time by about two thirds while still reviewing every high-risk unit in full. Agents run the first four layers on every unit regardless.

Every deliberate difference, such as a bug you chose to fix instead of copying, goes into a decisions log with a reason. When parity tests fail later, the log tells the reviewer which differences are expected.

Technique 6: Measure the QA team itself

An agent QA team needs QA too. Seed a few known bugs into ported units, such as a dropped validation rule, a changed sort order or a missing error state, and check that the layers catch them. If a seeded bug slips through, you know which layer to strengthen before a real one does.

Track the reviewer's precision as well: the share of its findings that the verifier confirms. A reviewer that cries wolf gets ignored, and a reviewer that never finds anything is probably not looking hard enough. Both numbers belong on the project dashboard next to units migrated.

  • Seeded-bug catch rate: which layer caught each planted defect, and which missed it.
  • Reviewer precision: confirmed findings divided by all findings.
  • Escaped defects: bugs found after merge, traced back to the layer that should have caught them.

What this looks like in MigrateX

Our MigrateX plugins build this in. Each framework plugin has a read-only parity-reviewer agent that checks one migrated piece against its original for behavior, markup, data and timing differences before the old code is removed. It can't edit code, so it can only report. The porter fixes, the reviewer checks again, and an engineer signs off.

Key takeaways

  • Modernization QA is about parity with an undocumented system, not meeting a spec.
  • Stack gates from cheap to expensive: static checks, characterization tests, parity review, visual and network diffs, human sign-off.
  • Write tests from the old code before the port, and lock them.
  • Keep the reviewer in a separate context, ask for concrete findings, and verify findings before acting on them.
  • Spend human review on the riskiest units, and log every deliberate difference.

Related reading: how multi-agent AI speeds up application migration, 10 legacy modernization mistakes to avoid and software migration lessons from Shopify's AI rewrite.


Originally published on the CobuildX blog.

Top comments (1)

Collapse
 
aicodeops_dev profile image
AI CodeOps •

Great framing: the bottleneck moves from writing the new code to proving it behaves like the old. Characterization tests plus maker-checker review is a solid combo. One trap we'd add for the layered gates: make sure each gate's result is pinned to the exact version it checked. With agents iterating quickly, it's easy for a test pass or a review approval to quietly carry over to a later commit that nobody actually verified.