Part 2 of 9Harness Engineering

What Test Harnesses Taught Us Before AI

Five design lessons from test harnesses that still define reliable automation—and now shape how we build coding-agent environments.

A hand-drawn test harness loop arranging state, acting on a system, observing results, and deciding with an oracle

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.

A controlled test world where arrange, act, observe, and decide form a loop around the system under test.
Figure 1A test becomes trustworthy when the harness controls the conditions that connect an action to its evidence.

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.

Five connected layers of trustworthy automation: isolated state, repeatable action, controlled dependency, independent oracle, and legible evidence.
Figure 2Trust compounds across five layers; a weakness in any one of them can make a passing result less meaningful.

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.

A fast local test loop uses a controlled double while a separate periodic contract test compares that double with the real dependency.
Figure 3Use doubles for deterministic local feedback, then check their fidelity against the real contract on a separate cadence.

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:

  1. What changed?
  2. Which expectation did it violate?
  3. 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 mapping from test harness concepts to agent harness concepts: fixtures to workspace snapshots, drivers to tools, doubles to sandboxes, oracles to evaluators, and results to actionable evidence.
Figure 4Agent harnesses reuse the responsibilities of test harnesses, but apply them to repositories, tools, runtimes, and human review.

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

Read more

All posts
  1. A hand-drawn map showing the Harness Engineer between developer experience, platform engineering, quality engineering, and AI systems
  2. A hand-drawn verification pyramid rising from deterministic checks to human judgment
  3. A hand-drawn stack of six harness layers from context and tools to feedback and verification
  4. A hand-drawn system map placing a model inside context, tools, runtime, task state, verification, and human control