Before coding agents, software teams already had a smaller autonomy problem: how do we let a machine run behavior, judge the outcome, and return a result we can trust?
The answer was not “write more assertions.” It was to design a controlled world around the code. That world—the test harness—made state repeatable, dependencies replaceable, outcomes observable, and decisions explicit.

This is the first lesson for harness engineering: reliability is a property of the whole environment, not only of the component doing the work.
A test suite asks questions; a harness controls the experiment
The ISTQB glossary defines a test harness as the collection of stubs and drivers needed to execute a test suite. That compact definition separates the questions from the machinery required to ask them.
A test case says what behavior matters. A fixture establishes the starting state. A driver invokes the system. A stub or fake controls a dependency. An oracle compares actual behavior with an expectation. A runner schedules the work and reports the result.
If any of those surrounding parts are weak, a correct assertion can still produce a misleading signal. Shared state may leak from an earlier test. The clock may cross a boundary mid-run. A network dependency may be unavailable. A mock may encode an implementation detail instead of a user-visible contract. The final message may say “failed” while hiding where the evidence diverged.
The harness therefore owns more than execution. It owns the causal chain between setup, action, outcome, and judgment.
Five properties compound into trust
The useful legacy of test harnesses is not one framework API. It is a set of system properties that survive changes in language, architecture, and tooling.

Isolated state prevents one run from contaminating the next. JUnit creates a fresh fixture per test; the deeper principle is that the starting point must be knowable.
Repeatable action means the same explicit trigger exercises the same boundary without relying on remembered manual steps or hidden options.
Controlled dependencies let the harness force conditions that are rare, expensive, slow, or dangerous in production: a timeout, a malformed response, an empty database, or a clock at midnight. Control makes difficult behavior reachable on demand.
An independent oracle judges success outside the producing code path. Assertions, snapshots, property checks, and evaluators create a separate boundary where shared mistakes are less likely to pass.
Legible evidence shortens diagnosis. Preserve the failing assertion, relevant state, narrow logs, diffs, screenshots, or traces that explain the mismatch—not only a boolean gate.
None of these properties is exclusive to tests. Together they form a general architecture for reliable automated work.
Test doubles trade fidelity for control
Replacing a real dependency is powerful because it makes behavior fast and deterministic. It is also risky because the replacement can drift away from reality.
Martin Fowler uses test double as the umbrella term for dummies, fakes, stubs, spies, and mocks. The distinctions matter less than the engineering question: what uncertainty are we removing, and what new uncertainty are we introducing?
A stub can return a timeout every time, which is far easier than waiting for a real payment provider to fail on cue. A fake database can keep tests fast and isolated. A spy can record whether a notification was sent. A mock can verify a specific interaction. Each gives the harness control, but each also moves part of the experiment away from production behavior.

The solution is not to reject doubles or pretend they are real. It is to separate two jobs. The fast loop asks whether our code behaves correctly under controlled conditions. A contract or integration check asks whether those conditions still represent the real dependency.
This separation is a durable harness pattern: optimize the common feedback loop for speed, then place slower fidelity checks at the boundary where drift can enter.
Evidence is part of the interface
A harness does not finish when execution stops. It finishes when the result is understandable enough to guide the next action.
Google’s guidance for end-to-end tests recommends preserving relevant state such as overview logs, screenshots, and database snapshots so failures are easier to debug. That advice exposes an important design boundary: diagnostic output is not incidental logging. It is the interface between execution and correction.
Good evidence is selective. Ten thousand log lines can hide the one transition that matters. A screenshot without the failing viewport or interaction state creates ambiguity. A snapshot diff that includes volatile timestamps trains people to ignore noise. More output is not automatically more observability.
A useful result answers three questions:
- What changed?
- Which expectation did it violate?
- What is the smallest context needed to reproduce or correct it?
This is why test quality and feedback quality cannot be separated. If a failure does not help the next move, the loop is technically closed but operationally weak.
The same pattern now surrounds agents
A coding agent works across a larger and less predictable world than a unit test, but the responsibilities are familiar.

A fixture becomes a known repository checkout, seeded database, or reproducible workspace. A driver becomes a tool runtime that can edit files, execute commands, and open the application. A stub becomes a sandbox or controlled service double that limits real-world effects. An oracle becomes a test suite, evaluator, policy check, or human approval. A test result becomes a reviewable diff plus the logs, screenshots, and traces needed to judge it.
The boundary expanded; the engineering questions did not.
Can we reproduce the starting state? Can the system reach the behavior we care about? Are effects controlled? Is success judged independently? Does the output make the next decision easier?
A capable model cannot answer those questions by itself because they describe the environment around the model. That is why the history of Harness Engineering begins with testing rather than prompting: test harnesses taught us how to turn automated action into trustworthy evidence long before an agent could write code.
The lesson to carry forward
The deepest contribution of the test harness was not automatic pass or fail. It was the idea that autonomy becomes reliable only inside a deliberately designed environment.
For today’s Harness Engineer, the practical move is to inspect every agent workflow using the same five properties:
- establish isolated, reproducible state;
- expose repeatable actions through narrow tools;
- control dependencies and side effects;
- keep verification independent from generation;
- return evidence that points toward correction.
The agent is new. The systems problem is not.
Sources
- ISTQB — Standard Glossary of Terms Used in Software Testing
- JUnit — Frequently Asked Questions
- Martin Fowler — Test Double
- Martin Fowler — Contract Test
- Google Testing Blog — What Makes a Good End-to-End Test?
