Chapter 31
Drive the body plan with tests
Use acceptance, unit, substitution, and live proofs to make the body plan visible.
On this page
Wednesday morning began with a failing test around the whole journey. It fed the system the Wednesday tray, one unready case, provenance, and a powder station that would fault. The first implementation retried. The test caught it.
The code now contains classifiers, carriers, a gate, a specification, a press, stations, recovery assessment, completed and halted results, final inspection, and a production orchestrator.
That list is not how the body plan was discovered. The test proposed what had to enter, what had to remain visible, where control had to return, and what the caller needed back. The objects appeared as the inner loop made each part of that protocol precise.
Tests are not a certification step after object design. They are how the relationships become visible soon enough to change.
Tests reveal different anatomy
The lab uses several test shapes. Each answers a different design question:
| Test shape | What it proves in the body | Lab example | What it refuses to prove |
|---|---|---|---|
| System acceptance | the body can walk through real owned wiring | reloading_lab_spec.rb and production_run_spec.rb |
physical bench behavior, third-party integration |
| Focused unit query | one object's incoming question returns the promised answer | classifier, gate, carrier, press recovery specs | that all objects are wired together |
| Focused command interaction | an orchestrator sends an owned side-effecting message at the joint | production_run_fault_spec.rb expects one feed and one cycle |
collaborator internals or physical execution |
| Substitution proof | changing a collaborator changes observable behavior | tighter caliber specification changes admission | universal plug-in support |
| Fake at a seam | deterministic behavior makes a system path executable | fault-once station in the acceptance test | fidelity beyond the protocol the outer test exercises |
| Live proof | the real system survives its real environment | observed case-processing pass; later live proofs on the ledger | behavior outside the conditions exercised |
An acceptance test is the body walking. A unit test defines a joint or reflex. A contract test proves two implementations fit the same socket. A fake is another working limb. A mock is a temporary sensor on an outgoing command. A stub supplies one canned nerve impulse.
Choose the lightest anatomy that proves the claim. A value object with no collaborators wants real values. An orchestrator whose only observable effect is an outgoing command may need a mock. A vendor adapter and its in-memory fake need the same contract examples. The live bench still owes a proof no Ruby double can provide.
Map the actual suite
The test map makes coverage and gaps visible:
OUTER LOOP
reloading_lab_spec
-> real CaseClassifier
-> real ProductionReadinessGate + CaliberSpecification
-> real ProgressivePress
-> real FactStation
-> FakeFaultOnceStation at the station seam
-> real PressRecovery
-> real ProductionRun
-> HaltedRun
production_run_spec
-> same owned production wiring with all real fact stations
-> FinalInspection
-> RunResult + BatchRecord
INNER LOOP
CaseClassifier spec ------------ three exits
CaliberBatch spec -------------- carrier invariant and refusal
ProductionReadinessGate spec --- facts, unknown, reasons, policy swap
ProgressivePress spec ---------- per-position truth and held plate
PressRecovery spec ------------- deterministic disposition
ProductionRun fault spec ------- orchestration and command count
RunResult spec ----------------- shared completion query
LIVE PROOFS OUTSIDE THE SUITE
case feed + decap + size + headspace loop --- observed
everything after the gauge ------------------ open (see the Lab Ledger)
This map catches an easy overclaim. The Ruby acceptance test uses real objects inside the miniature's boundary, but the miniature has no router, database, or vendor adapter. Calling it a production end-to-end test would inflate the evidence. It is an end-to-end system test for this plain-Ruby model.
The fault-once station is not a fake of a third-party SDK. It is an owned test station that speaks the press protocol and produces one deterministic fault. The outer test catches whether it actually composes with the real press. If a real external station adapter appears later, it and its fake will need a shared contract covering success and the failures production cares about.
lib/production_run.rb. Boxes are objects the tests can swap; arrows are the messages the specs pin.Red proposes the body plan
The fault slice followed an outside-in sequence.
First, the system test stated the behavior that did not exist:
it "stops at the first station fault instead of silently retrying" do
result
expect(charge.attempts).to eq(1)
end
The red result was informative. The application called the station twice. The plumbing worked; the behavior was wrong.
Second, a focused orchestrator test defined the outgoing commands at the joint:
it "commands one feed and one cycle before returning control" do
expect(press).to receive(:feed).with(
have_attributes(cartridge_case: ready_case)
).once
expect(press).to receive(:cycle).once.and_return(cycle_result)
run.call([ready_case], provenance: {})
end
Those doubles represent types the lab owns: classifier, gate, press, recovery, and final inspection. The test does not mock Ruby, time, randomness, or a third-party client. It verifies the side-effecting messages the orchestrator is responsible for and ignores the collaborators' implementations.
Third, the result protocol was made explicit. A halted run had to answer
halted? with true; a completed run received a regression test for false.
The two result types could then differ honestly in every other field.
Only after those tests were reviewed did implementation change. The production
constructor now receives one grouped line containing the press and recovery
assessment:
def initialize(classifier:, gate:, line:, final_inspection:)
@classifier = classifier
@gate = gate
@press = line.press
@recovery = line.recovery
@final_inspection = final_inspection
end
The grouping is not a generic dependency bag. Press and recovery share the same ordered stations and change together as one production line. It preserves the four-collaborator constructor without hiding unrelated dependencies.
The outer test then went green. The full suite stayed green.
Red proposed the behavior and its visible joints. Green supplied the smallest implementation. Refactor improved the local DNA without changing who talks to whom. Skipping directly to a polished class design would have silenced that conversation.
A joint must move the result
Injection alone can be ceremonial. The readiness gate could accept a specification and ignore it while using a hard-coded length. That constructor would look clean and the body plan would still be false.
The substitution test changes policy while the case stays fixed. The verdict must change. That observable consequence proves the joint.
Use the same standard for adapters and fakes. A contract test should run the same examples against both implementations, including important failure modes. If the real adapter times out and the fake always succeeds, the two limbs do not fit the same socket no matter how similar their method names are.
And look for bypasses after the pattern appears. An injected payment gateway does not make a boundary real if a controller also calls Stripe directly. A registry does not own construction if tests build a different graph from production. The most dangerous body plan has two nervous systems: the one in the diagram and the one tired code reaches for.
Keep nerves visible
The lab uses no current time, environment configuration, randomness, logger, or job queue. If it did, those would be collaborators at the boundary, not language conveniences reached from domain code.
Time.now, SecureRandom.uuid, ENV[...], global logging, and job dispatch
all change behavior. Hidden access makes tests reach for global stubs and
travel helpers. Injection makes the nerve visible and permits a small fake:
frozen clock, sequential identifier, explicit config, array-backed logger or
queue.
Defaults can remain real at the application composition root. Domain objects should not need to know whether a collaborator is real or fake. The shared message protocol is the joint.
Make a test map before implementation
For your selected slice, fill this map:
| Claim | Test level | Real collaborators | Substitution | Observable result | What this test cannot prove |
|---|---|---|---|---|---|
| Body walks | acceptance | ||||
| Decision rule | unit query | ||||
| Outgoing command | unit interaction, if needed | ||||
| Adapter fit | shared contract | ||||
| Failure path | acceptance plus focused tests | ||||
| Production reality | live proof |
Then run the seven body-plan questions against the tests:
- What level is this?
- Can I draw the message flow?
- Where are the injected joints?
- What globals or vendors are reached directly?
- What decision does each object own?
- What does each object refuse to know?
- Where could the tested path be bypassed?
A hard test may be tooling pain or domain consequence. Often it is the body plan diagnosing itself. A constructor stub, message chain, or forty-line setup is evidence worth interpreting before adding another helper.
Two ways green lies
Gamed green. The spec builds the object differently from production, or asserts something a hard-code can satisfy. The gamed spec in The Body Plan's danger section is the worked example, and its fix is two moves on the spec itself: construct the object the way production does, and assert behavior a hard-code cannot fake. Do both and the right design becomes the only implementation that passes.
Unverifiable green. The mirror image, hiding behind an honest red. A walking-skeleton acceptance test is supposed to be red on exactly one deferred assertion. But the runner aborts an example at its first failing expectation, so if the deferred-red assertion sits mid-example, every assertion after it never executes. The "everything else passes" claim is unverifiable. Put the honest-red assertion last, or split it into its own example. And when reading any red: one failing expectation is not evidence that the lines below it pass. They simply did not run.
Gamed green lies about what the test proves. Unverifiable green lies about what the test even ran. Guard against both when you author, and check for both when you read.
The live run
The live-proof row of the anatomy deserves its own warning. A gateway wrote to an external tool, with complete unit coverage against a fake; the real tool was never touched by the suite. Everything the tests could prove, they proved. The one thing they could not prove is that the real write lands. One controlled live run proved it: make the change, read it back, restore the original state.
"Only a live run proves the outward write" is true, and dangerous if you generalize it casually. Tie live proof to a dry-run gate, a sandbox or test account, an idempotency key, replay protection, and a cleanup path. Especially for payment, email, certification, identity, enrollment, or anything that writes to a partner.
Production can fail a green model
The readiness specs first proposed five facts. Review found that sized was
missing, so a regression test made the six-fact rule explicit. The suite went
green.
Then production found another omission. Initial cleanliness did not mean the
case was free of sizing lubricant and preparation residue before loading.
Complete drying became a separate release obligation. The headspace gauge also
made the result of sizing more useful than a historical sized fact alone.
The green suite was not fraudulent. It proved the model that had been proposed at that point. It could not prove that the model contained every physical fact. The outside loop was still larger than Ruby.
That is why the code in this tutorial still carries the six-fact snapshot. The planned final cleaning has not run, so the exact replacement rule is not ready to drive another implementation. Preserving the earlier test and naming its limit shows the design conversation. Silently rewriting it would make the current answer look inevitable.
What green does not mean
Thirty-seven examples pass (browse the project, or download and run it). They prove the executable claims in the miniature. They do not prove the physical live-ammunition line is safe, the readiness rule is complete, the quarantine procedure is sufficient, or an operator can execute recovery. The bench has separately proved case feed, decapping, full-length sizing, spent-primer capture, headspace inspection, and swaging for one bounded 5.56 set. It has not proved the next configuration.
Tests make the software body plan inspectable. They do not turn a model into its environment.
The final chapter removes the ammunition and asks you to use the questions, artifacts, and test loop on a system the garage cannot answer for you.