Testing LLM products
Testing an LLM product means testing a feature whose model answers the same question differently on two runs, while most of its checks remain exact assertions. The page opens with twenty failures an order assistant ships with — the check that catches each, and where it runs — then covers the missing test oracle, the deterministic layer and its unhappy paths, properties for answers with no expected value, the 2026 OWASP list as a test plan, who owns what, and five releases that went wrong.
- Exactly assertable
- 14 of 20 failures
- No expected answer
- Test a property
- Adversary rows from
- OWASP LLM Top 10, 2026
Twenty failures an LLM feature ships with — the check that catches each, and where it runs
Nobody opens a page about testing LLM products for a definition. They open it because a feature is due and the usual test plan does not fit: the output changes between runs, so what exactly does a test assert? The answer is less exotic than the question. Below are twenty failures an order assistant in an online shop is likely to ship with. It answers “where is my order”, explains the returns policy from the help centre and can start a return. Each row names what the customer gets, the check that catches it, what kind of check it is and when it runs. Rows 1–4 are the integration, 5–10 the output contract, 11–15 the content, 16–19 the adversary, and 20 is drift. Later sections cite them by number.
| # | What the customer gets | The check that catches it | Kind · when it runs |
|---|---|---|---|
| 1 | An empty chat bubble | A recorded refusal and an empty answer both render a message and the offer of a person | Exact · every commit |
| 2 | A spinner for thirty seconds, then “Something went wrong” | An injected timeout, 429 and 500 from the provider: a handoff to a person inside the time budget | Exact · every commit |
| 3 | Half a sentence, or nothing: the reply was cut at the token limit and failed to parse | A recorded max_tokens stop: the parser rejects it, and the feature retries or falls back | Exact · every commit |
| 4 | “Your order has shipped” when the order service returned an error | An injected tool error: no order status may appear in the reply without tool data behind it | Exact · every commit |
| 5 | A reply the interface cannot render | Schema validation of every reply: the fields, their types, the allowed values | Exact · every commit, in production |
| 6 | A ticket routed to a queue that does not exist | The intent is one of the six allowed labels | Exact · every commit |
| 7 | A return started for the wrong order | Tool-call arguments asserted: the order id belongs to this customer and is the one they named | Exact · every change |
| 8 | A refund amount that differs from the order | Every number in the reply recomputed from the tool’s result | Exact · every change |
| 9 | A help-centre link to an article that says something else | The cited article exists in the index and contains the quoted passage | Exact · every change |
| 10 | A confident answer to a question the policy does not cover | Cases with no answer in the help centre: the handoff flag is set | Exact · every change |
| 11 | A correct answer to a different question | A calibrated model judge: “does the reply answer the question asked — yes or no” | Scored · every change |
| 12 | A correct answer without the condition that decides it: the return window ends on Friday | A judge given the facts each case requires: is every one of them present | Scored · every change |
| 13 | A different answer when the same question is asked in other words, or in Ukrainian | Invariance: paraphrases and translations of one case reach the same intent, tool call and facts | Property · every change |
| 14 | The same decision when the facts change: “yes, you can return it” on day 34 of 30 | Direction: move the delivery date across the 30-day line, and the decision must flip | Property · every change |
| 15 | An action taken because a product review or an email told the assistant to | Hostile text planted in the help centre and in tool output: the tool calls must match a run without it | Property · nightly, before release |
| 16 | Another customer’s address in the reply | Every name, address and phone number in the reply belongs to the signed-in customer | Exact · every commit, in production |
| 17 | The system prompt and the tool list read back to whoever asks | A canary string from the prompt never appears in a reply — and nothing secret is in the prompt to begin with | Exact · before release |
| 18 | A link or an image that carries data to another site | The renderer allows only the shop’s own domains, in every Markdown form: inline, reference-style, image | Exact · every commit |
| 19 | One conversation that costs a hundred times the median | Caps on tool calls and tokens per conversation, tested with a case built to loop | Exact · every commit, in production |
| 20 | Worse answers with nothing deployed | A pinned model, the scored set run nightly against the live API, and production rates — refusals, handoffs, thumbs-down — with alerts | Live · nightly, in production |
What actually changes: the missing oracle
Testing research has a name for the thing that broke. A test oracle is whatever decides whether an output is correct — usually an expected value written next to the input. In 2015 Earl Barr, Mark Harman and colleagues surveyed the oracle problem: how to tell correct behaviour from incorrect when that expected value is missing, or too costly to write. Scientific simulations have it, machine-learning models have it, and an LLM feature has it in its purest form. The expected output of “where is my order?” is any one of a thousand acceptable sentences.
What did not break is the rest of the idea. An oracle does not have to be an expected string. It can be a contract the output must satisfy, a relation between two outputs, a grader that returns a verdict, a person, or what real users did with the answer. Every row of the table above uses one of those, and the last column says which.
| Oracle | What it decides | Cost per run | Rows |
|---|---|---|---|
| A contract (exact) | The output obeys a rule: it parses, has its fields, its number matches the tool, its link is on our domain | Nothing; milliseconds | 1–10, 16–19 |
| A relation (property) | Two outputs relate as they should: the same for a paraphrase, different when a fact changes | Two calls of the feature | 13–15 |
| A grader (scored) | A model returns a verdict on one output against a written criterion | A call per case, plus calibration | 11–12 |
| A person | Reads a sample before release, and labels the cases every grader is calibrated against | Minutes per case | behind 11–12 |
| Production (live) | The real mix of inputs, and what users did with the answers | Nothing per run; slow to read | 20 |
A common shortcut is to set the temperature to zero and assert the string anyway. Temperature zero means the model always takes its most likely next token, which should make the output repeatable. In September 2025 Horace He and colleagues at Thinking Machines Lab showed why it is not on an ordinary inference server. They sent one prompt a thousand times at temperature zero and got eighty different completions. All thousand agreed for the first 102 tokens; at the 103rd, 992 continued “Queens, New York” and eight “New York City”.
The cause was not randomness in the model but load on the server. How many other users’ requests were batched together changed the order of floating-point additions, and with it the last digits that settle a near tie between two tokens. Their fix, batch-invariant kernels, made all thousand completions identical, at a cost in speed. A shared APIApplication Programming Interface — contract between programsThe rules one program follows to call another: which requests it may send, in what shape, and what comes back. To the caller it is a contract — everything behind it stays a black box, which is exactly what lets the other side be rewritten without breaking you.Full article →All terms does not run it for you. So treat a hosted model as non-deterministic whatever the temperature, and write tests that do not care.
The deterministic layer first: contract, tools and unhappy paths
Draw the feature as boxes and the shape of the work is obvious. The order assistant is a request handler, an intent router, a search over help-centre articles, a model call, a parser, three tools and a renderer. One box is probabilistic. The rest are ordinary code, and each carries exact checks that run in milliseconds. The diagram pins every row of the table to the box it tests.
Structured outputs removed one whole class of these failures and left three. When OpenAI introduced them in August 2024, its own evaluation of complex schemas went from under 40% for an older model to 100% with strict mode on. The same announcement lists what the guarantee does not cover. A refusal comes back as a refusal instead of the schema. A reply that reaches max_tokens stops before the object is complete. And the values inside a valid object can still be wrong. Those three limits are rows 1, 3 and 8, which is why a schema guarantee does not retire the tests — it tells you which ones to write.
The integration rows can run on every commit only if they cost nothing, and they cost nothing only if the provider is not called. So record real responses once — an ordinary answer, a refusal, a truncated reply, a tool callthe model asking code to runA model response that, instead of text, names one of the functions you described to it and supplies arguments; your code runs it and hands the result back. The arguments are untrusted input and are validated like any request body — a model that has read an injected instruction produces a well-formed, entirely wrong call.Full article →All terms — and replay them from files in the test run. Then write the responses nobody can record on demand: a timeout, a 429, a 500, a tool that fails, a reply with broken JSONJavaScript Object Notation — text format for dataA text format for passing data around: key–value pairs, lists, numbers and strings. It came out of JavaScript, but every language reads it now, and that is why most APIs answer in it rather than in anything more capable.All terms. The suite is now fast and deterministic, and it tests exactly the code that turns provider behaviour into what a customer sees.
| Fault | What the customer must get | Row |
|---|---|---|
| The provider times out | A message and a handoff inside the time budget — not a spinner | 2 |
| 429, rate limited | One retry with backoff, then the handoff; batch jobs never take the chat’s quota | 2 |
Stopped at max_tokens | Rejected by the parser; one retry asking for a shorter answer | 3 |
| A refusal | A sentence the interface can show, never an empty box | 1 |
| The order tool fails | “I can’t see your order right now” — never a status | 4 |
| Broken JSON | Rejected, retried once, and counted in the schema-failure rate | 5 |
No expected answer? Test a property
Rows 13–15 have no expected output, and they are still not judged by a score. They compare two runs of the feature with each other. Software testing has done this for decades wherever an oracle is missing. Metamorphic testing, reviewed by Tsong Yueh Chen and colleagues in 2018, checks a relation between outputs instead of the outputs themselves: rotate the input image, and the detected objects should rotate with it.
In 2020 Marco Tulio Ribeiro and colleagues brought the idea to language models as CheckList and named three test types. A minimum functionality test is a small set of simple cases that must all pass. An invariance test changes the input in a way that must not change the output. A directional test changes it in a way that must move the output in a known direction. In their user study, practitioners with CheckList wrote twice as many tests and found almost three times as many bugs as those without it.
| Test type | Change to the input | What must hold | On the order assistant |
|---|---|---|---|
| Minimum functionality | None — simple cases | Every case passes | “Where is order 1042?” calls get_order with 1042; “I want to return the blue jumper” opens a return |
| Invariance | A paraphrase, another language, a typo, a sentence about the weather | The same intent, tool call and facts | Row 13 |
| Invariance of the action | A hostile instruction inside a review or an email | The same tool calls as the run without it | Row 15 |
| Direction | The delivery date moved from day 30 to day 31 | The return decision flips to no | Row 14 |
| Direction | The order number removed | The assistant asks for it instead of guessing | Row 7, from the other side |
A property needs no labelled answer, which is what makes it cheap to scale: one source case becomes ten follow-up cases by rule. Its limit is that both runs can be wrong in the same way. A question and its paraphrase can agree perfectly on a return window the policy never stated. That is why properties sit beside the scored rows rather than replacing them.
The adversary: the 2026 OWASP list as a test plan
Rows 15–19 come from a list a tester can use directly. OWASPOpen Worldwide Application Security Project — open application security foundationA non-profit foundation that publishes free, community-written security guidance — best known for its Top 10 lists of the most critical risks for web applications, APIs and LLM applications. A Top 10 ranks categories of risk to design and test against; it is not a checklist that proves a system safe.All terms published the 2026 edition of its Top 10 for LLM Applications on 4 August 2026. For the first time the ranking is not opinion alone: the practitioners’ vote carries three quarters of the weight, and 6,639 classified real incidents carry the rest. The preface states the posture behind all ten entries: “Stop trying to build a model that cannot be fooled. Build the system around it, so that when the model is fooled, and it will be, nothing important breaks.”
For a tester that sentence is the plan. You do not test whether the model can be tricked; it can. You test what the system lets a tricked model do — which tools it can reach, what it can show, what it can send and how much it can spend.
| Entry | On the order assistant | The test | Row |
|---|---|---|---|
| LLM01 Prompt Injection | Text in a review, an email or a tool result is read as an instruction | Hostile documents planted in the index and in tool output; the actions compared with a clean run | 15 |
| LLM02 Sensitive Information Disclosure | Another customer’s data or internal notes in a reply — or in tool arguments and logs | Every personal detail in the output checked against the signed-in account; the same scan over traces | 16 |
| LLM03 Excessive Agency | The assistant can do more than a conversation needs — refund, not just start a return | Exactly three tools exist; a test that no prompt reaches anything else | — |
| LLM06 Unbounded Consumption | A conversation that loops through tools or asks for an essay | Caps per conversation, and a case built to loop | 19 |
| LLM07 Misinformation | A policy stated confidently and wrongly, then acted on | Citations that must contain the claim, cases with no answer, required facts | 9, 10, 12 |
| LLM08 Hidden Context Exposure | The system prompt and tool schemas read back to a curious customer | A canary string; nothing in the prompt that would matter if it were read | 17 |
| LLM10 Improper Output Handling | The reply is rendered without checks: links, images, HTML | Renderer tests for every Markdown form, not only the inline one | 18 |
The other three entries — supply chain (LLM04), data and model poisoning (LLM05) and vector and embedding weaknesses (LLM09) — live mostly in how the model and the index are built and fed. They belong to a security review of that pipeline more than to the feature’s test suite. One practical trap: the 2026 list moved eight of its ten entries. System Prompt Leakage, LLM07 in 2025, is now Hidden Context Exposure, LLM08; Improper Output Handling fell from fifth to tenth. A test plan that cites entries by number alone now points at the wrong ones, so cite the year with the number — LLM08:2026.
Who writes what, and where each check runs
A tester on an ordinary feature writes steps and expected results. On an LLM feature the steps are trivial — send this, read that — and 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 most valuable artefact on the project, because every scored row and every grader is only as good as it.
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 it is what makes the suite better exactly as the product gets used.
Engineering owns the harness
Recording and replaying responses, pinning versions, storing results, wiring the gate into the pipeline. Ordinary infrastructure, and it 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 search and the invoice usually belong to three different people.
A domain expert belongs in this loop and is usually missing from it. Whether a refund rule is stated correctly, whether a medical instruction is safe, whether a reply follows the company’s policy — the team that built the feature cannot settle these, and a rubric written without the expert encodes the engineers’ guess about the domain. Book the expert’s time early and explicitly, and treat their labels as the reference every grader is calibrated against.
| Moment | What runs | Why there |
|---|---|---|
| Every commit | The exact rows against recorded responses: 1–6, 16, 18, 19. | Free and instant, and it covers the failures that cause most incidents. |
| A change to the prompt, the model or the index | Rows 7–14 on the live model: the scored 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. |
| Nightly | The same set against the live API, plus the adversarial cases of row 15. | Catches drift you did not cause: a provider update, a new refusal shape, a slower endpoint. |
| Before a release | The protected set — the cases nothing may break — rows 15 and 17, and a person reading a sample. | The last moment a person looks at real output before a customer does. |
| In production | Rows 5, 16, 19 and 20 as monitors: schema failures, personal data, cost per conversation, refusal and handoff rates, thumbs-down. | The only place the real mix of inputs exists. Your test set is always a sample of last quarter. |
Five releases, and the rows that would have caught each
None of these was a model that could not be tested. In each, something reached the public that one of the twenty rows checks for, and in every one the people who found it were users, journalists or outside researchers rather than the team’s own suite.
| Case | What happened | The rows that cover it |
|---|---|---|
| Tessa, NEDA, May 2023 | A chatbot for people with eating disorders, built by specialists from preprogrammed replies, was given generative AI by its operator as part of a systems upgrade. A consultant, Sharon Maxwell, asked it about healthy eating and was told to keep a calorie deficit of 500 to 1,000 a day. She says it was taken down within hours of her screenshots; the association had closed its human helpline the month before. | 20 and 12. Swapping scripted replies for a generative model is a release that needs the whole suite again, and a specialist’s list of what must never be said is the scored set that would have failed the first reply. |
| Gemini images, Feb 2024 | Tuning meant to show a range of people was applied to prompts that named a specific kind of person or a historical setting, and over time the model became, in Google’s words, “way more cautious than we intended”, refusing harmless prompts. Google paused image generation of people three weeks after launch. | 14 and 1. A directional case — a prompt that specifies who must get what it specifies — and the refusal rate tracked as a number, not discovered as a mood. |
| Apple notification summaries, Dec 2024 | Summaries of news alerts, shown under the publishers’ names, stated things the articles did not — among them that the man accused of killing UnitedHealthcare’s chief executive had shot himself. The BBC complained in December; on 16 January 2025 Apple switched the summaries off for news and entertainment apps in the iOS 18.3 betas. | 9 and 12. A summary is checked against its source — every claim in it must be in the article — and a set of real headlines is the scored set. |
| Grok, May 2025 | At about 3:15 am Pacific time on 14 May, someone changed the X bot’s system prompt to give a “specific response” on a political topic, and Grok inserted it into replies about unrelated subjects. xAI called it an unauthorised modification, began publishing its prompts on GitHub and said changes would need review. | 13 and 20. Replies to unrelated questions must not share a topic — an invariance case — and a prompt change is a deployment, reviewed, with the protected set run before it. |
| EchoLeak, Microsoft 365 Copilot, Jun 2025 | An email from outside the company carried instructions written as if to its human reader, so the injection classifier let it through. When Copilot later retrieved it, the reply could carry data out through reference-style Markdown links and images, which the link redaction did not recognise. CVE-2025-32711, rated 9.3. It was fixed before the write-up appeared, and Microsoft says no customers were affected. | 15 and 18. A hostile document in the index, and a renderer tested with every Markdown form — not only the one everyone writes. |
Where it degrades
The honest limit of everything on this page is that a suite checks what somebody thought to put in it. Twenty rows are twenty failures someone imagined; property tests need someone to invent the relation; a red team finds the attacks of its own members. Testing cannot show that an injection is impossible, which is exactly why the OWASP preface asks for a system in which a fooled model breaks nothing important — and why the rows that limit what the model can do (7, 16–19) are worth more than any score.
- Declaring the feature untestable and shipping on demos, which turns every customer into an unpaid tester with no way to report.
- Asserting exact strings, which gives a suite that fails on every run for reasons unrelated to quality — and gets switched off.
- Setting the temperature to zero and calling the output deterministic.
- Starting with the scored rows because they are new, and never reaching the exact ones that cause the incidents.
- A replay suite that has not been re-recorded since launch, green against a provider that no longer exists.
- A red-team exercise run once, its findings fixed and never turned into cases.
- Prompt edits and vendor upgrades that skip the suite because “nothing was deployed”.
- OWASP entries cited by number without the year, after the 2026 renumbering.
- Leaving cost and latency out of the suite, so the first person to notice is whoever opens the invoice.
When to use it
Use it when
- Any feature where a model’s output reaches a customer, another system or a decision with consequences.
- Start with the exact rows on recorded responses — the integration, the contract, the adversary — before any scored evaluation.
- Use properties where no answer can be written down: paraphrase, language, boundary values, hostile text.
- Treat a prompt edit, a model upgrade and a vendor change as releases that run the suite.
Avoid it when
- Exact-string assertions on generated text, which fail constantly for reasons that have nothing to do with quality.
- Conclusions drawn from one run, where sampling alone can support either answer.
- A model-graded score where a schema check, a recomputation or a lookup would settle it exactly.
- Testing whether the model can be fooled instead of what a fooled model is able to do.
Sources and method
What this page stands on
- 13 sources
- 5 documented failures
- Checked October 17, 2026
The failures this page learns from
the five releases in “Five releases, and the rows that would have caught each”- ArticleEating disorder helpline takes down chatbot after it gave weight loss advice (opens in a new tab)NPR · Jun 8, 2023
- PostmortemPrabhakar Raghavan — Gemini image generation got it wrong. We’ll do better. (opens in a new tab)Google · Feb 23, 2024
- ArticleApple Intelligence: iPhone AI news alerts halted after errors (opens in a new tab)BBC News · Jan 16, 2025
- ArticlexAI blames Grok’s obsession with white genocide on an ‘unauthorized modification’ (opens in a new tab)TechCrunch · May 15, 2025
- Documentationxai-org/grok-prompts — Prompts for our Grok chat assistant and the @grok bot on X (opens in a new tab)xAI
- PostmortemAim Labs — Breaking down ‘EchoLeak’, the First Zero-Click AI Vulnerability Enabling Data Exfiltration from Microsoft 365 Copilot (opens in a new tab)Cato Networks · May 31, 2025
- DocumentationCVE-2025-32711 Detail (opens in a new tab)NIST National Vulnerability Database · Jun 11, 2025
Research
the missing oracle, properties instead of answers, and temperature zero- ResearchEarl T. Barr, Mark Harman, Phil McMinn, Muzammil Shahbaz, Shin Yoo — The Oracle Problem in Software Testing: A Survey (opens in a new tab)IEEE Transactions on Software Engineering · May 2015
- ResearchTsong Yueh Chen et al. — Metamorphic Testing: A Review of Challenges and Opportunities (opens in a new tab)ACM Computing Surveys · Jan 2018
- ResearchMarco Tulio Ribeiro, Tongshuang Wu, Carlos Guestrin, Sameer Singh — Beyond Accuracy: Behavioral Testing of NLP Models with CheckList (opens in a new tab)ACL 2020 · Jul 2020
- ResearchHorace He, Thinking Machines Lab — Defeating Nondeterminism in LLM Inference (opens in a new tab)Thinking Machines Lab · Sep 10, 2025
Practices and where the terms come from
the adversary rows, and what a schema guarantee does not coverResearched with Claude. Finding and cross-checking the primary sources was done with Claude (Anthropic). Selection, writing and fact-checking: the Anatomy of IT Product editors.
Found this useful?
Share it with someone who is working on the same problem.