Why Testing an Agent Differs from Testing Deterministic Code
Deterministic unit tests assert that the same input always yields the same output.
Agent systems break that contract because models sample, tool results drift, and multi-step plans take different paths to the same goal.
You still need tests.
You need a layered strategy: hard assertions where code is deterministic, and evals where quality is graded rather than string-matched.
Stops when a stopping condition fires or a budget is exhausted.
Emits a final answer whose wording is not unique.
Two healthy runs can call tools in different orders, phrase answers differently, and still complete the task.
Two broken runs can look fluent while skipping a required tool or inventing facts.
Report scores with sample size and version pins (prompt hash, model slug, tool schema version).
# Sketch: hard success is deterministic; wording is not compareddef task_passed(run) -> bool: return ( run.final_status == "completed" and "create_ticket" in run.tools_called and run.ticket["priority"] == "P1" and run.cost_usd < 0.50 )
A model or prompt change is a behavior change, not a docs-only tweak.
Treat eval gates the way you treat integration tests for a payment service: block deploys when pass rate or safety scores drop below agreed thresholds (see CI lifecycle docs in this category).
Yes. Use pytest (or any unit framework) for tools, parsers, and host policy. Use the same runner to orchestrate eval jobs that assert on pass rates and thresholds, not only on raw strings.
What should fail a PR immediately?
Deterministic regressions (tool bugs, schema breaks, policy bypasses) and hard safety failures. Soft quality dips may warn on PR and block only on larger suites.
How many trials per case do I need?
Start with 1 for pure unit tests, 3-5 for flaky model paths on critical cases, and more for release gates. Raise N until the pass-rate confidence interval is useful for your risk level.
Should I mock the model always?
Mock for orchestrator and tool-wiring tests. Use live or recorded-but-refreshed model calls for quality evals. Mocking the model forever hides real prompt regressions.
How do I test open-ended answers without a judge?
Extract structure (JSON schema, required citations, entity sets), check tool trajectories, and reserve free-form prose for rubric scoring when structure is not enough.
Are snapshot tests ever useful?
Yes for stable structured artifacts (tool schemas, prompt templates, fixture JSON). Avoid snapshotting free-form model prose as a hard gate.
What is the difference between an eval and a test?
A test usually expects a fixed result. An eval measures quality against criteria, often aggregating many cases and runs into scores and thresholds.
How do tool flakiness and model flakiness interact?
External tools add their own nondeterminism. Sandbox or script external systems in CI so failures point at the agent, not at a flaky third-party API.
Do multi-agent systems change the picture?
They add handoff and privilege surfaces. You still unit-test tools, but scenarios must cover wrong handoffs and shared-state races.
When is exact match acceptable on model output?
When the model is constrained to a tiny closed set (enums, yes/no, fixed JSON schema with strict decoding) and you still allow for rare provider failures with retries.
How should product managers read eval reports?
As task success rates, cost per success, and safety incident counts - not as "the prompt looks better."
What is the first step if our suite is only manual demos?
Extract pure tools into unit tests, write ten gold tasks with hard success checks, and run them on every model/prompt change before adding fancy judges.