10.0 What This Teaches
assertEqual. This tutorial covers:
- Why AI output resists traditional unit tests
- Structural tests: things you can check deterministically
- Semantic tests: running generated code and checking its behavior
- Using
temperature=0to improve test stability - Snapshot (golden file) testing for regression detection
- Human-in-the-loop evaluation for subjective quality
10.1 Why AI Output Is Hard to Test
assertEqual(response_text, expected)
for anything beyond trivial prompts. Instead, test properties of the output:
does it contain the right structure? Does the code it generated actually run?
10.2 Structural Tests
# structural_test.py - check that the response contains a function and parses.
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=512,
temperature=0,
messages=[{"role": "user", "content": "Write a Python function that adds two numbers."}]
)
text = response.content[0].text
# Structural check 1 - does the response contain a function definition?
assert "def " in text, "Response does not contain a function definition"
# Structural check 2 - does the code block parse as valid Python?
try:
compile(text, "<string>", "exec")
print("Syntax OK")
except SyntaxError as e:
print(f"Syntax error in generated code: {e}")
compile() parses the source without executing it. It catches syntax
errors but not logic bugs - that requires running the code.
10.3 Semantic Tests
exec() executes a string as code in a namespace you
control:
# semantic_test.py - exec the generated function and test its behavior.
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=256,
temperature=0,
messages=[{"role": "user", "content":
"Write only a Python function named add(a, b) that returns a + b. "
"No explanation, just the function."}]
)
code = response.content[0].text
# Execute the generated code in an isolated namespace
namespace = {}
exec(code, namespace)
# Call the generated function with known inputs
add = namespace["add"]
assert add(2, 3) == 5, f"Expected 5, got {add(2, 3)}"
assert add(0, 0) == 0, f"Expected 0, got {add(0, 0)}"
assert add(-1, 1) == 0, f"Expected 0, got {add(-1, 1)}"
print("All semantic tests passed.")
exec() to fail.
10.4 Temperature=0 for Reproducibility
temperature=0, the model picks the highest-probability token at
every step. This does not guarantee identical output across model versions, but it
is far more stable than the default temperature. For automated tests, always set
temperature=0:
# Always use temperature=0 in automated test scripts.
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=512,
temperature=0, # makes output as stable as possible
messages=[{"role": "user", "content": "Write a Python function to reverse a string."}]
)
10.5 Snapshot Testing
# snapshot_test.py - generate output, save it, compare on later runs.
import anthropic
from pathlib import Path
client = anthropic.Anthropic()
GOLDEN_FILE = Path("golden_reverse.txt")
PROMPT = ("Write only a Python function named reverse_string(s) that returns s reversed. "
"No explanation.")
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=256,
temperature=0,
messages=[{"role": "user", "content": PROMPT}]
)
current = response.content[0].text
if not GOLDEN_FILE.exists():
GOLDEN_FILE.write_text(current)
print("Golden file created.")
else:
golden = GOLDEN_FILE.read_text()
if current == golden:
print("Snapshot matches.")
else:
print("SNAPSHOT MISMATCH - output changed since baseline was saved.")
print("--- golden ---")
print(golden)
print("--- current ---")
print(current)
10.6 Human-in-the-Loop Evaluation
# human_eval.py - generate N responses and prompt the user to rate each.
import anthropic
client = anthropic.Anthropic()
PROMPT = "Explain what a Python context manager is. Be clear and concise."
NUM_RESPONSES = 3
scores = []
for i in range(NUM_RESPONSES):
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=256,
# deliberately not setting temperature=0 to get varied responses
messages=[{"role": "user", "content": PROMPT}]
)
text = response.content[0].text
print(f"\n--- Response {i + 1} ---")
print(text)
score = input("Rate this response 1-5: ").strip()
scores.append(int(score))
print(f"\nScores: {scores}")
print(f"Average: {sum(scores) / len(scores):.1f}")
10.7 Exercise
Exercise
Write a test script that:
-
Asks Claude to write a Python function that returns
Trueif a string is a palindrome (ignore case). -
Extracts the code from the response by finding the first line that starts
with
defand taking everything from there to the end. -
Uses
exec()to load the function into a local namespace. -
Calls the function on
["racecar", "hello", "level"]and asserts the correct results:True,False,True. -
Prints
"All tests passed."if all assertions succeed.
10.8 Common Mistakes
Testing exact output text with assertEqual
# Bad - breaks whenever the model rephrases the same correct answer.
assert response.content[0].text == "def add(a, b):\n return a + b\n"
# Good - test behavior, not exact text.
exec(response.content[0].text, ns := {})
assert ns["add"](2, 3) == 5
Not setting temperature=0 for automated tests
# Bad - default temperature causes flaky tests.
response = client.messages.create(model="claude-sonnet-4-6", max_tokens=256,
messages=[{"role": "user", "content": "Write a sort function."}])
# Good - pin temperature to reduce variation.
response = client.messages.create(model="claude-sonnet-4-6", max_tokens=256,
temperature=0,
messages=[{"role": "user", "content": "Write a sort function."}])
temperature=0
in automated test scripts.
exec()ing model output without any sanity check
# Risky - no check before executing arbitrary model output.
exec(response.content[0].text)
# Safer - compile first to catch syntax errors, then exec in an isolated namespace.
code = response.content[0].text
compile(code, "<string>", "exec") # raises SyntaxError if malformed
ns = {}
exec(code, ns)
compile() before exec() and always use an isolated
namespace rather than exec(code) directly into the global scope.
10.9 Key Terms
| Term | Meaning |
|---|---|
| structural test | A test that checks the shape or format of the output without running it |
| semantic test | A test that runs the generated code and checks its behavior with known inputs |
| exec() | Python built-in that executes a string as code; used to run model-generated functions |
| compile() | Python built-in that parses source code and catches syntax errors without executing it |
| golden file | A saved reference output used to detect changes in future runs |
| snapshot test | A test that compares current output against a saved golden file |
| temperature=0 | Setting that makes model output as deterministic as possible; required for stable automated tests |
| human evaluation | Manual scoring of model output for subjective qualities that automated checks cannot measure |