QASenior

Testing LLM products

Testing an LLM product means testing a feature that can answer the same question differently twice, and every testing practice you have assumes the same input gives the same output. An LLM feature breaks that, and the usual reaction is to conclude that it cannot be tested — which is wrong, and expensively so. Most of what matters about an LLM feature is still exactly assertable, the part that is not becomes a measurement against a threshold, and the job of a tester shifts from writing steps to defining what a good answer is. This page is about that shift and what to do on the Monday after it.

The assertion becomes
A measurement
Test first
What is still exact
One run is
Not evidence

What actually changes, and what does not

One thing changes: you can no longer assert that the output equals an expected string. Everything else that follows — that a single run proves little, that results are read as rates, that you need a set of cases rather than a case — is a consequence of that one fact rather than a new discipline.

The whole difficulty of testing an LLM feature fits in one change. On the left is the test you already know: the same input produces the same output, so the assertion is an equality and the verdict is binary. On the right the same input produces a different answer on every run, so an equality is not merely strict — it is meaningless. The assertion becomes a grader that returns a number and a threshold that decides, and a single run stops being evidence: you look at a rate across many cases rather than a pass on one. What the row underneath adds is the half that gets forgotten in the excitement. A great deal about an LLM feature is still exactly assertable — that the response parses as valid JSON matching your schema, that no personal data appears in it, that the expected tool was called with the expected arguments, that it arrived inside the latency and cost budget. Those checks are instant, free and unarguable, and they should carry as much of your suite as they possibly can before any score is involved.

The right-hand panel is where the attention goes, and the bottom row is where the work should start. Before any score is involved, a large share of what a product owner actually cares about can be checked exactly: that the response is valid JSON matching the schema the interface expects, that the classification is one of the four allowed labels, that the tool call carried the right arguments, that no personal data appears in the text, that the answer arrived within the latency budget and under the cost ceiling. These are ordinary tests with ordinary assertions, and they belong to the same suite as everything else you test.

Build the deterministic layer first

Before writing a single scored evaluation, cover the things that have exact answers. They are cheap, they run in milliseconds, they never disagree with themselves, and each one closes a failure mode that would otherwise reach a user.

CheckWhat it prevents reaching users
The response parses and matches the schemaA blank screen or a crash when the model returns prose where the interface expected fields.
The label is one of the allowed valuesA fifth category invented on the spot, silently routing tickets to a queue nobody reads.
The tool was called, with these argumentsAn agent that answers confidently from memory instead of looking anything up.
Every citation exists in the source and contains the claimA plausible answer attached to a document that says something else — the most damaging failure in a retrieval product.
No personal data, no secrets, no internal identifiers in the outputA regulatory incident, which is not a bug report but a letter from somebody’s lawyer.
The refusal cases still refuseA prompt change that made the assistant more helpful and also more willing to do what it must not.
Latency and cost per call stay inside budgetA feature that works perfectly and is switched off at the end of the month for what it costs.

The role shift: who writes what

A tester on a conventional feature writes steps and expected results. On an LLM feature the steps are trivial — send this, read that — and all the difficulty moves into defining what a good answer is. That definition is a specification, it is genuinely hard to write, and it is the single most valuable artefact on the project.

  • QA owns the definition of good

    The written rubric: what must be present, what must never be, what tone, what to do when the answer is not known. Without it, every review is an argument about taste.

  • QA owns the failure corpus

    Every complaint, incident and surprising answer becomes a case. Curating this is the work that makes the suite get better exactly as the product gets used.

  • Engineering owns the harness

    Running the set, pinning versions, recording results, wiring the gate into the pipeline. This is ordinary infrastructure and should be treated as such.

  • One named owner for the number

    Somebody must be able to block a release on quality. An LLM feature has no natural owner — the prompt, the retrieval and the invoice usually belong to three different people.

A domain expert belongs in this loop and is usually missing from it. Whether a legal summary is correct, whether a medical instruction is safe, whether a support answer follows policy — none of these can be settled by the team that built the feature, and a rubric written without that expert encodes the engineers’ guess about the domain. Book the expert’s time explicitly, early, and treat their labels as the reference every automated grader is calibrated against.

Where each check belongs in the pipeline

Scored evaluations cost money and take minutes, so they cannot run on every commit the way unit tests do. That constraint produces a natural tiering, and writing it down stops the two failure modes: a suite so expensive it is quietly disabled, and a release process where nothing checks quality at all.

MomentWhat runsWhy there
Every commitDeterministic checks against recorded responses — schema, parsing, routing, error paths.Free, instant, and it covers the failure mode that actually causes most incidents.
A change to the prompt, model or retrievalThe full scored evaluation set, compared case by case with the recorded baseline.These three are the deployable artefact. A change to any of them is a release, whatever the ticket says.
NightlyThe same set against live providers, plus the safety and refusal suite.Catches drift you did not cause — a provider change, a moderation update, a slower endpoint.
Before a releaseThe protected set — the cases nothing is allowed to break — plus a human review of a sample.The last moment a person looks at real output before a customer does.
In productionOnline checks on real traffic: schema failures, refusal rate, latency, cost per call, thumbs-down rate.The only place the real input distribution exists. Your evaluation set is always a sample of last quarter.

The failures worth testing for on purpose

Some failure modes belong to this class of product specifically, and none of them will be found by testing the happy path. Each deserves a small permanent set of cases.

  • Instructions arriving inside content the model reads. A document, an email or a web page can contain text aimed at your system, and if retrieved content is treated with the same authority as your prompt, it can be. Test with hostile documents on purpose.
  • The confident wrong answer. Ask questions whose answers are not in the source at all; the correct behaviour is to say so, and the default behaviour is often to produce something plausible instead.
  • A model version that changed under you. Provider aliases move; if your pin is an alias, your system upgraded itself in production with no diff, no review and no rollback path.
  • Long inputs. Behaviour at the edge of the context window differs from behaviour in the middle of it, and truncation usually removes the instruction rather than the noise.
  • Languages and formats other than the ones in the demo. A product tested only in English will meet its first Ukrainian address, its first PDF table and its first emoji on the day it launches.

Anti-patterns worth naming

  • Declaring the feature untestable and shipping on demos, which converts every customer into an unpaid tester with no reporting channel.
  • Asserting on exact strings, producing a suite that fails on every run for reasons unrelated to quality.
  • Judging a change from one run of one prompt, where sampling alone can produce either conclusion.
  • Building an evaluation platform before there is a feature and a handful of real failures to put in it.
  • Leaving cost and latency out of the test suite, so the first person to notice is whoever opens the invoice.

When to use it

Use it when

  • Any feature where a model output reaches a user, an external system or a decision with consequences.
  • Start with the deterministic layer — schema, tools, citations, safety, budget — before any scored evaluation.
  • With a written rubric agreed with a domain expert, because otherwise every review is an argument about taste.
  • With online checks in production, since that is the only place the real distribution of inputs exists.

Avoid it when

  • Exact-string assertions on generated text, which fail constantly for reasons that have nothing to do with quality.
  • Conclusions drawn from a single run, where the sampling alone is enough to support either answer.
  • A model-graded score where a schema check, a recomputation or a lookup would settle it exactly.
  • Treating the whole feature as unknowable, which hands away the ordinary software testing that catches most incidents.

Found this useful?

Share it with someone who is working on the same problem.