How to Test AI Features: Validating Non-Deterministic Software
Traditional tests assert that an input produces one exact output. AI features break that assumption — the same prompt can return different, equally valid answers. Here is how to test software whose correctness is a range, not a value.
Every testing tool ever built rests on one assumption: given the same input, the system produces the same output. assertEqual(add(2, 2), 4) works because addition is deterministic. That assumption is now false for a growing share of the features teams ship. An LLM-powered summarizer, a semantic search, a support chatbot, a recommendation engine — feed them the same input twice and you may get two different, both-correct answers.
This is not a bug to be stamped out. Non-determinism is inherent to how these systems work, and often to their value. But it means the entire vocabulary of testing — exact assertions, golden files, snapshot comparisons — stops applying to the feature itself. You cannot assert that the chatbot returns this exact string, because the correct behavior is a whole space of acceptable strings.
Testing AI features is not traditional testing with more retries. It is a different discipline. Here is how it actually works.
Separate the deterministic shell from the probabilistic core
The first and most important move is to draw a line. Almost no AI feature is entirely non-deterministic. Around the probabilistic core there is a deterministic shell — and most of your existing testing skill applies to the shell unchanged.
- Deterministic shell: the API accepts the request, validates input, enforces auth, respects rate limits, handles a model timeout gracefully, returns valid JSON, records the interaction. All of this is exactly as testable as any other code, with exact assertions.
- Probabilistic core: the actual content of the model's answer.
A huge amount of "AI feature" reliability lives in the shell — and teams routinely neglect it because they are intimidated by the core. Test the shell hard and with ordinary tools. Then, and only then, deal with the core differently.
Test properties, not exact outputs
For the probabilistic core, replace "does the output equal X?" with "does the output satisfy the properties a correct answer must have?" You are no longer checking a value; you are checking membership in the set of acceptable answers.
// Deterministic thinking (wrong for AI): brittle and almost always fails.
expect(summary).toBe("The meeting is at 3pm on Tuesday.");
// Property-based thinking (right for AI): assert what MUST hold.
expect(summary.length).toBeLessThan(originalText.length); // it actually summarizes
expect(summary).toContain("3pm"); // key fact preserved
expect(summary).toContain("Tuesday"); // key fact preserved
expect(detectLanguage(summary)).toBe("en"); // same language
expect(await containsHallucination(summary, originalText)).toBe(false);
Properties come in families you can reuse across features:
- Format and structure — valid JSON, required fields present, length within bounds, no leaked system prompt.
- Factual grounding — every claim in the output is supported by the provided source (the anti-hallucination check).
- Constraint adherence — it refused a disallowed request, stayed on topic, respected the requested tone or language.
- Safety — no toxic content, no PII leakage, no injection payload echoed back.
None of these require knowing the exact answer. They encode what any correct answer must satisfy — which is precisely the definition of correctness for a non-deterministic system.
Use a model to grade a model — carefully
Some qualities are too fuzzy for a hard-coded check. "Is this a helpful answer?" "Is the tone appropriate?" For these, the practical technique is LLM-as-judge: a second model evaluates the first model's output against a rubric.
This works, but it has to be done with discipline, because you have introduced a second non-deterministic system to judge the first:
- Give the judge a rubric, not a vibe. "Score 1–5 on whether every fact is grounded in the source" beats "is this good?" Specific criteria produce stable judgments.
- Prefer pairwise comparison over absolute scores. Models are more reliable at "is A better than B?" than at "rate A out of 10."
- Validate the judge itself. Hand-label a set of examples, then confirm the judge agrees with humans before you trust it at scale. An unvalidated judge is just a second source of noise.
Measure distributions, not single runs
Because the same input yields different outputs, a single test run tells you almost nothing. A pass might be luck; a fail might be a rare sample. The unit of truth for an AI feature is a distribution, not a data point.
Run each evaluation case many times and track the rate: this prompt produces a grounded answer 98% of the time, refuses correctly 99% of the time, leaks PII 0% of the time. Now you have something you can regression-test. When a new model version, a prompt change, or a retrieval tweak ships, you compare distributions: did the grounded-answer rate drop from 98% to 91%? That is a regression you can see, quantify, and block on — something no single assertion could ever surface.
This also reframes "flakiness." For deterministic code, a test that passes 98% of the time is broken. For an AI feature, a 98% grounding rate might be exactly the spec. The job is not to eliminate variance; it is to measure it and hold it to a threshold.
Guard the boundaries in production
Offline evaluation cannot cover the inputs real users invent. AI features therefore need runtime guardrails that are themselves tested deterministically: validate the model's output before it reaches the user, strip or block unsafe content, fall back to a safe default when the output fails its property checks, and log every interaction so failures are reproducible. The model may be a black box, but the gate in front of it does not have to be.
Where BuniOD fits
Testing AI features stretches every team, because it demands two things at once: rock-solid deterministic testing of the shell, and property-and-distribution-based evaluation of the core — maintained continuously as models and prompts change underneath you.
BuniOD approaches quality as behavior over the flows that matter, which is exactly the framing non-deterministic features require: it validates that a feature does what it should rather than that it returns one exact string, and it confirms failures by reproducing them before raising an alert — the difference between a real regression and a single unlucky sample. For products built on AI, that closes the gap between "the model usually works" and "we can prove the feature holds, release after release." You can read about the security model or how it fits QA teams.
Conclusion
AI features break the one assumption testing was built on, but they do not break testing. They split it in two. The deterministic shell — validation, auth, error handling, output parsing — is tested exactly as it always was, and is where most real-world failures actually occur. The probabilistic core is tested against properties rather than exact values, graded by validated judges where properties won't reach, and measured as a distribution rather than a single run.
The teams shipping reliable AI products are not the ones who found a way to make models deterministic. They are the ones who stopped demanding that they be — and built a testing discipline that measures behavior as a range, holds it to a threshold, and guards the boundary in production.
Quality intelligence, in your inbox
Occasional, high-signal writing on AI testing and release quality. No spam.
We'll only email you about new articles. Unsubscribe anytime.
See your software through AI
Connect your product, describe the flow you need covered, and get a reliable scenario in minutes.
Request Access