User Acceptance Testing

The build team proved it runs. Your job is to prove it does the work.

User acceptance testing (UAT) is the final stage before go-live, where the business that paid for custom software confirms the system does the real job it was commissioned to do. It checks that the software handles the actual work correctly, not merely that it runs without errors, since the build team has already verified that.

This page is written for the person who commissioned the software and now has to decide whether to accept it, not for a QA team or a test manager. UAT is the buyer's decision, and the rest of this page is the builder's side telling you how to make it properly.


UAT vs system testing: the build team's job and yours

The difference between UAT and system testing is what decides whether your sign-off protects anything. You have committed a large sum to the custom web application you commissioned, and once you accept it, the pressure to get faults fixed moves onto your side of the table. System testing, run by the build team and its QA, confirms the software runs to the functional specification. UAT confirms it does the job. The developer has already proved the code works; your task is to prove it does the work.

What the build team already checkedWhat only you can check
It runs without errorsYour recurring Tuesday jobs go through start to finish
Buttons and screens workYour awkward-customer edge cases survive
It meets the functional specification technicallyThe numbers match what you would do by hand
Data saves and loadsThe software fits how the work actually happens

The rows on the left are engineering checks: a QA pass, and increasingly an operational acceptance testing step for backups and uptime, will have cleared them before delivery. The rows on the right need someone who knows the work, running a real scenario from start to finish rather than a scripted click-through. Skip the distinction and you either repeat the QA testing the build team already did, or you sign off having never tested the one thing only you could. Alpha and beta testing do not apply here: those are pre-release consumer ideas for products with many users, not a standard for accepting one commissioned system.

The line to hold: system testing verifies the software runs to the specification. UAT verifies it fits how the work actually happens.


Who on your side should actually test it

The expected answer is your end users, the business users who will live in the system after go-live. That answer is right but too soft. Sharpen it: the person who should run UAT is the daily operator who knows the awkward cases, the refund that needs a manager override, the order that arrives with three lines cancelled, the customer whose account was merged years ago. Those cases are where custom software breaks, and only someone who processes them every week will think to try them.

Right: the person who does the job daily and already knows which cases are awkward.
Right: the sceptic who pushes at the edges rather than confirming the happy path works.
Avoid: the project sponsor who signs off without ever touching the system.
Avoid: the build team's own people, who test against what they built, not what the work demands.
Avoid: a brand-new hire with no feel for what "normal" looks like.

That person is usually your busiest operator, which is exactly why UAT stalls. Testing properly takes focused days, so book the time and cover their day-job for the window. Squeezed into spare moments, it becomes a rubber stamp.


Test scripts from your real work, not "have a click around"

Ask a busy operator to "have a click around and tell us what you think" and you get a shrug, a remark about the font, and maybe two reported typos. What you do not get is the failure that matters: the order that dispatches without decrementing stock, the discount that silently fails to apply. Unstructured poking finds cosmetic faults and misses the ones that cost money on day two.

A test script fixes that. UAT test cases are just written lists of steps derived from a real recurring job, and every step carries a concrete expected result, so a pass or a fail is unambiguous and anyone can run it without you standing over them. When a step fails, those same numbered steps become the steps to reproduce, handed straight back to the developer. Most of these scripts already exist in draft form as the workflows you have already documented: the procedure becomes the script, the expected result becomes the acceptance criterion. Test the happy path first, then the edge cases, then the full run that crosses several screens from order to invoice.

Three sources cover most of what breaks.

The Tuesday-afternoon job

The routine, high-volume task the business runs dozens of times a day. If this is awkward, the software fails quietly and constantly.

The month-end run

The periodic heavy process: statements, invoicing, reconciliation. It touches the most records and surfaces the worst edge cases.

The awkward customer

The refund with a manager override, the split delivery, the account on non-standard pricing that breaks the normal rules.

Turn one of these into steps. Raising and dispatching a customer order reads clearly as a test case, with every step written so its expected result cannot be fudged.

StepWhat you doWhat should happen
1Create a new order for an existing customerThe saved delivery address and pricing tier load automatically
2Add three in-stock products and one out-of-stock productThe out-of-stock line is flagged and cannot be added to dispatch
3Apply the customer's agreed discountLine and order totals recalculate; the discount shows on the order
4Confirm and dispatch the orderStock decrements by the quantities ordered; status moves to Dispatched
5Open the customer's accountA dispatch confirmation email is sent and logged against the order
6Reopen the order the next dayTotals, discount and dispatch record are unchanged

Six rows like this, each expected result specific enough that it cannot be fudged, and you have a test that means something. A structured Google Sheet or Excel file with these three columns is all an SME needs to run its UAT.


Test with real data, not "Test Customer 1"

The scripts from the last section need records to run against, and the records you reach for by default are the wrong ones. "Test Customer 1" with a round-number order and a tidy local postcode passes every script you write, because it was built to. The software then meets real data for the first time on day one, in front of a real customer, and the failures land in production instead of testing. Clean fake data proves the happy path works; it proves nothing about the shape of your actual records.

The failure mode: Test Customer 1 passes. Real customer data breaks on day one.

Messy real records surface the failures that matter:

  • Names with apostrophes, non-Latin characters, or unusual length
  • Fields the old system never required, now sitting empty
  • Historical records migrated across from the previous system
  • Overseas or non-standard addresses
  • Part-payments, refunds, and credit notes
  • Records that predate the current pricing or tax rules

Build the test dataset from a copy of real production data with personal details anonymised, not a handful of invented tidy rows. Representative data hits the edge case during UAT, while a fix is cheap, rather than after go-live.


Agree acceptance criteria before the build, not at UAT

Acceptance criteria are the agreed, testable statements of what "done and correct" means for each feature. The previous sections turned expected results into criteria at the script level. The larger point is timing: those criteria belong in writing before the build starts, tied to the functional specification (or business requirements document, if that is what your project calls it) and to the contract, not discovered on the day you sit down to test. When they are settled during discovery and carried into the build as the Definition of Done, UAT becomes a check against something both sides already signed off.

Invent them at UAT time and acceptance turns into a renegotiation. A criterion raised for the first time in the test room is really an argument about whether the feature was ever in scope, and that argument rarely ends cleanly. Stated as a single line, a criterion reads plainly:

Given an order over £500, When it is submitted, Then it routes to a manager for approval

The governing principle: acceptance criteria agreed before the build make UAT a confirmation, not a renegotiation.

Each criterion either passes or it does not. That binary is the raw material of the go/no-go call.


Running UAT: the environment, the process, the defect loop

UAT testing runs against a copy of the system, never the live one. That copy is a staging environment or vendor sandbox: production-like, loaded with the realistic data from the previous stage, where a tester can break a workflow without touching real customers or real invoices. The run itself is a loop rather than a single pass, one discrete stage in the build process it belongs to, sitting just before go-live.

1. Prepare
Load the staging environment with the realistic test data.
2. Run the scripts
Work through each test script the way the person who does that job actually would.
3. Log the defect
One entry per defect, with steps to reproduce and a screenshot or a Loom recording attached.
4. Fix
The developer diagnoses and resolves it.
5. Re-test
Do not assume the fix landed. Re-run the failing script, then run regression testing around it.
6. Sign-off
Record which criteria now pass.

Track defects wherever the team already works. A shared Google Sheet is the honest default for most SME projects; Jira does the same job with more overhead if the developers already live there. The screen recording is what matters: it turns "it's broken" into something a developer can reproduce in one viewing. In phased delivery, UAT is a fixed window before a big-bang cutover; in agile, you accept each increment as it ships. Set aside one to three weeks depending on system size, not an afternoon.


The go/no-go call: blocker or cosmetic

Every logged defect now has to collapse into one decision: accept the build and go live, or reject it and hold. The pile of tickets from testing is not the decision, it is the input to it. The danger runs both ways. Over-index on trivia and you delay go-live for months over wording nobody reads; wave something through to hit a date and you ship a system that corrupts orders in the first week. Severity classification is what keeps the go/no-go decision honest.

Give each defect one of four labels so the room is arguing about the same thing:

  • Blocker: stops go-live outright.
  • Major: hurts, but has a workaround.
  • Minor: a nuisance that can wait.
  • Cosmetic: presentation only.

The labels only matter if they map to action. Everything below blocker is a judgement about whether the fix waits until after launch. The check below turns your defect log into a UAT checklist: run each open item past it before you sign anything.

Blocks go-live: a core recurring job cannot complete.
Blocks go-live: data is lost, corrupted, or written back wrong.
Blocks go-live: a money, legal, or compliance error with no workaround.
Blocks go-live: no viable workaround exists for a task that has to happen.
Does not block: cosmetic wording or layout that reads oddly but functions.
Does not block: a rare edge case that already has a workaround.
Does not block: a nice-to-have that was never core to the brief.
Does not block: anything with a documented, agreed first-week fix.

Hold one rule when the room disagrees. If a defect corrupts data, blocks a core daily job, or gets money or compliance wrong with no workaround, it blocks go-live. Almost everything else is a documented first-week fix.


Sign-off without signing away your position

Sign-off is the moment the money and the standing move. Once you accept the build, the pressure that made the team responsive drains away, and unpaid fixes become favours. UAT ends in one of three states, and which one you pick decides how much hold you keep afterwards. Treat the choice as a negotiating position, not a formality.

Full acceptance

Every test case passes, no blockers stand open, nothing on the list is worse than cosmetic. Sign off clean and move on.

Conditional acceptance

No blockers, but a documented snag list of the minor and cosmetic items from your defect triage, with an agreed fixing window after go-live. You launch on time without waiting on trivia, and the outstanding work stays contractually owed.

Rejection

A blocker stands. The build is not fit to go live. Back round the loop, re-test, and hold sign-off until it clears.

Full sign-off releases the pressure on the build team entirely. Conditional sign-off keeps it: the remaining items are named, dated, and owed, so a launch delay over one cosmetic defect never becomes your problem. That is how we run acceptance with the people we build for, and it is why the snag list is written down before anyone signs.

Ready to run acceptance properly

If you have commissioned software and want the sign-off stage structured so it protects you, we can set it up.

Structure your acceptance stage →