Why LLMs Broke Testing
Fifty years of software testing rests on one move: run the code, assert the output equals the expected value. The moment a language model enters the loop, that move stops working — and understanding exactly why is the foundation of the whole job.
Let's make it concrete. You build a summarizer and write the test you've written ten thousand times:
The test that betrays you
def test_summary():
out = summarize("The cat sat on the mat in the warm afternoon sun.")
assert out == "A cat rested on a mat in the sun." # one blessed answer
Run it five times:
| Run | Output | Good? | == expected |
|---|---|---|---|
| 1 | "A cat rested on a mat in the afternoon sun." | Yes | FAIL |
| 2 | "A cat sat on a mat, warmed by the sun." | Yes | FAIL |
| 3 | "In the afternoon sun, a cat sat on a mat." | Yes | FAIL |
| 4 | "A cat lounged on a mat in warm sunlight." | Yes | FAIL |
| 5 | "A cat rested on a mat in the sun." | Yes | PASS |
Five good summaries. Exact-match accuracy: 20% — and 0% if run 5 hadn't matched by luck. The metric measures phrasing, not quality. There are thousands of equally good summaries, so "correct" is replaced by "good," and good is a distribution, not a point.
The deeper trap: even your score is noisy
So you ditch exact match for a quality scorer (0–1 per case). You run 20 cases: 0.80. Next week, new prompt, 20 cases: 0.85. You ship a "5-point win." You just fooled yourself. A score over n cases has a margin of error — the rough 95% interval is 1.96 × √(p(1−p)/n):
| n | Score | 95% interval | What you can honestly say |
|---|---|---|---|
| 20 | 0.80 | ±0.18 (0.62–0.98) | Almost nothing |
| 200 | 0.80 | ±0.055 (0.74–0.85) | "Roughly 80%" |
| 2000 | 0.80 | ±0.018 (0.78–0.82) | "80%, confidently" |
At n=20 your "win" is smaller than the noise. This is the shift that trips up every engineer from deterministic testing: you stop thinking in test cases and start thinking in measurements with sample sizes. A score without an n is a rumor.
Traditional QA asks "is this output correct?" AI evaluation asks "how good is the system on average, across the cases I care about, with what confidence, and is it improving?" Every technique in this book follows from that one change.
Feel the noise: watch a confidence interval shrink
Pure simulation — no key, no spend. Pretend a system's true quality is 80%, "evaluate" it at three sample sizes, and watch the margin of error. Step 1: run it. Step 2: could a "0.85" and a "0.80" at n=20 be the same system? (Yes.) Step 3: set TRUE_QUALITY = 0.95 and rerun — why do the intervals tighten? (Because p(1−p) is largest at 0.5.)
Lab code — runs free
import random
from math import sqrt
TRUE_QUALITY = 0.80 # the system's real pass-rate (unknown in real life)
def simulated_eval(n):
passes = sum(random.random() < TRUE_QUALITY for _ in range(n))
p = passes / n
ci = 1.96 * sqrt(p * (1 - p) / n) # 95% confidence interval
return p, ci
random.seed(0)
for n in (20, 200, 2000):
p, ci = simulated_eval(n)
print(f"n={n:>4}: score={p:.2f} 95% CI = +/- {ci:.3f} "
f"({p-ci:.2f} to {p+ci:.2f})")
Output (with seed(0)):
n= 20: score=0.85 95% CI = +/- 0.156 (0.69 to 1.01)
n= 200: score=0.80 95% CI = +/- 0.055 (0.74 to 0.85)
n=2000: score=0.81 95% CI = +/- 0.017 (0.79 to 0.82)
At n=20 the interval (0.69–1.01) swallows almost any difference you'd care about — the 0.80-vs-0.85 "win" lives entirely inside the noise. The rule you just felt: to halve an interval you must quadruple the sample size (it scales as 1/√n). That governs how big every eval set you build needs to be — and it's why "we tried a few examples, looked great" is not an evaluation.
Quill: the five-point "win" that was noise
Quill, a writing-assistant startup, ran a quality scorer on their summarizer and measured 0.80. After a prompt change they measured 0.85 on the same 20-case set, declared a "5-point improvement," and shipped it. The problem: at n=20 the rough 95% interval is about ±0.18, so 0.80 and 0.85 are statistically indistinguishable — the "win" was sampling noise.
When they later re-ran both versions on 500 cases, the "improved" prompt was actually slightly worse. They had shipped a regression and celebrated it. Nothing was wrong with their scorer — the failure was reporting a difference smaller than the margin of error. The lesson that reframed their whole practice: a score without a sample size is a rumor, and a difference inside the noise band is not a result. They moved to fixed, larger eval sets and started quoting intervals, not just point scores.
One system runs through every chapter. Meridian, a Series-B fintech, is building Remi, a support assistant for billing questions. This chapter: their first test was an exact-match assertion — does Remi's reply equal the expected string? Four of five genuinely correct billing answers "failed" purely because Remi worded them differently. Same lesson as the chapter: "correct" gives way to "good," and good is a distribution — so they scrap exact match and commit to measuring Remi over many real questions with a sample size. (Ch 3: they build that dataset.)
Quiz · Chapter 1
- You score 20 cases at 85% this week; a different 20 scored 80% last week. Did quality improve?
- A perfectly good summarizer scores ~0% on exact-match accuracy because:
- The meaningful "unit of truth" in AI evaluation is:
- To halve your eval's confidence interval you should roughly:
- The most honest answer to "what's our accuracy?" is: