Test
Pattern: A named solution to a recurring problem.
“Testing shows the presence, not the absence, of bugs.” — Edsger Dijkstra
Understand This First
• Invariant – tests verify that invariants hold.
• Test Oracle – the oracle tells the test what the right answer is.
Context
You’ve built or modified software and you need to know whether it works. Not “probably works” or “looks right,” but an objective, repeatable answer. This is a tactical pattern, fundamental to every stage of software development.
A test builds on the idea of an Invariant or a Requirement: something the system should do or a property it should have. The test makes that expectation executable; it runs the code and checks the result.
Problem
Software behavior is invisible until you run it. Reading code can tell you what it probably does, but only execution reveals what it actually does. Manual checking is slow, unreliable, and doesn’t scale. How do you gain confidence that your software behaves correctly, and keep that confidence as the software changes?
Forces
• Manual verification is expensive and error-prone.
• Code that works today may break tomorrow after a seemingly unrelated change.
• Writing tests takes time that could be spent building features.
• Tests that are too tightly coupled to implementation become fragile and expensive to maintain.
• Without tests, you must re-verify everything by hand after every change.
Solution
Write executable claims about your software’s behavior. A test is a small program that sets up a situation, exercises a piece of code, and checks whether the result matches an expectation. If the result matches, the test passes. If not, it fails, and the failure tells you exactly where the problem is.
Tests come in many sizes. Unit tests check a single function or class in isolation. Integration tests check that multiple components work together. End-to-end tests simulate a real user interacting with the full system. Each level trades speed for realism: unit tests run in milliseconds but miss integration bugs; end-to-end tests catch more but run slowly and break easily.
The most important property of a good test is that it fails only when something is genuinely wrong. A test that fails randomly, or fails when you change an irrelevant detail, is worse than no test. It trains people to ignore failures.
How It Plays Out
A developer adds a function that calculates shipping costs based on weight and destination. They write three unit tests: one for a domestic package under 5 pounds, one for an international package, and one for a zero-weight edge case. Each test calls the function with specific inputs and asserts the expected output. These tests run in under a second and will catch any future change that accidentally breaks the shipping calculation.
In an agentic workflow, tests become the primary feedback mechanism for AI agents. When you ask an agent to implement a feature, the agent writes code, runs the tests, sees failures, and iterates. The tests act as a specification the agent can check against, a machine-readable definition of “done.” Without tests, you’re left reviewing every line of generated code by hand.
Note: Tests aren’t proof of correctness. They check specific cases you thought of. Bugs live in the cases you didn’t think of. Tests reduce risk; they don’t eliminate it.
Example Prompt: “Write unit tests for the calculate_shipping function. Cover domestic under 5 pounds, international, and the zero-weight edge case. Each test should call the function with specific inputs and assert the expected output.”
Consequences
A healthy test suite gives you confidence to change code. You can refactor, add features, or upgrade dependencies, and the tests will catch most breakage immediately. This is especially valuable when working with AI agents that change code rapidly.
The cost is maintenance. Tests are code, and code has bugs. When the system’s behavior changes intentionally, you must update the tests to match. A large, poorly organized test suite can become a drag on development, where every change requires updating dozens of tests. The remedy is to test behavior, not implementation details, and to keep tests focused and independent.
Related Articles
Complements: Agentic Manual Testing — Agentic manual testing covers flows scripted tests don't, and promotes successful checks into scripts over time.
Complements: Logging — Tests verify behavior before deployment; logging captures behavior after.
Complements: Observability — Tests verify before deployment; observability verifies after.
Contrasts with: Metric — Tests give a binary pass/fail on specific behaviors; metrics track continuous quantities over time.
Contrasts with: Service Level Objective — Tests prove specific behaviors work in a controlled environment; SLOs measure whether the running system keeps working under real conditions.
Depended on by: Backfill — A backfill leans on tests as the oracle for its read-cutover phase, with sampling and invariant checks against the corpus catching what unit tests cannot.
Depended on by: Eval — Many eval criteria rely on existing test infrastructure — the same green/red signal at a different unit of analysis.
Depends on: Determinism — Deterministic behavior is what makes tests repeatable and trustworthy; flaky tests usually trace back to a hidden source of nondeterminism.
Depends on: Invariant — Tests verify that invariants hold.
Depends on: Test Oracle — The oracle tells the test what the right answer is.
Detects: Silent Failure — Tests convert silent failures into loud ones.
Enables: Red/Green TDD — The TDD loop depends on working tests.
Enables: Refactor — Tests make refactoring safe.
Enables: Regression — Detection — tests catch regressions automatically.
Enables: Test-Driven Development — Tests become a design tool.
Related: Architecture Fitness Function — Tests verify behavioral correctness; fitness functions verify structural correctness. Both run in the same pipeline.
Related: Feedback Loop — Tests are the most common sensor in a development feedback loop.
Related: Harnessability — Test coverage is the foundation of feedback sensing; without tests, an agent has nothing to check its work against.
Related: Shift-Left Feedback — Tests are the most common feedback sensors to shift left — fast tests catch problems where they are cheapest to fix.
Related: Technical Debt — Missing tests are a common form of debt.
Supported by: Printf Debugging — When a test fails and the reason isn't obvious, printf debugging is the most common next step.
Tests: Failure Mode — Test each failure mode explicitly.
Tests: Happy Path — Happy-path tests are the baseline; the test suite's value comes from what it checks beyond them.
Tests: Performance Envelope — Load tests verify the envelope.
Used by: Code Review — Tests handle behavioral verification; reviews handle design verification. Both are needed.
Used by: Feedback Sensor — Tests are the most common computational feedback sensor: a sensor that runs the same way every time and emits a clear pass/fail signal.
Used by: Strangler Fig — Comparison tests between old and new implementations are the safety net that makes each replacement step trustworthy.
Used by: Test Impact Analysis — Test Impact Analysis selects from an existing test suite; the tests are the substrate it chooses among.
Uses: Fixture — The surrounding infrastructure that runs tests.
Uses: Harness — The surrounding infrastructure that runs tests.
Violated by: Vibe Coding — Vibe coding skips testing because it skips understanding what to test.
Sources
• Edsger Dijkstra articulated the fundamental limitation of testing — “Program testing can be used to show the presence of bugs, but never to show their absence!” — at the 1969 NATO Software Engineering Conference in Rome and in his Notes on Structured Programming (EWD 249, 1970). This observation, quoted in the epigraph above, remains the single most important thing to understand about what testing can and cannot do.
• Glenford Myers wrote The Art of Software Testing (1979), the first systematic treatment of software testing as a discipline with its own principles and techniques. Myers defined testing as “the process of executing a program with the intent of finding errors,” a framing that shifted the mindset from confirmation to falsification.
• Mike Cohn introduced the test pyramid in Succeeding with Agile (2010), originally sketched in conversation with Lisa Crispin around 2003-04. The pyramid’s layering of many fast unit tests, fewer integration tests, and a small number of end-to-end tests gave teams a practical model for allocating testing effort.
• Kent Beck formalized test-driven development in Test-Driven Development: By Example (2003), making executable tests the starting point of design rather than an afterthought. Beck’s work elevated tests from a verification tool to a first-class development practice.