| name | core-verify-test-authoring |
| description | Use when writing or revising tests in any language or framework — express one observable behavior at a public seam, keep cause and effect close, use independent expected values, prefer real boundaries over internal mocks, and make tests readable, deterministic, focused, and diagnostically useful. |
Test Authoring
The craft of tests after core-verify-testing-strategy has decided what evidence is needed.
Tests are executable examples and diagnostic tools. They should help a reader understand the
contract, fail when that contract is broken, and survive changes that preserve the behavior.
The red → green discipline and public-seam baseline are adapted from Matt Pocock's tdd;
the broader guidance is synthesized from the Google Testing Blog corpus linked below.
Start at a public seam
Name the behavior and the seam before writing the test. A seam is the public interface where a
caller, user, consumer, or system can observe the result without reaching inside the subject.
Use the acceptance criteria, bug report, or agreed test plan to choose it. Test implementation
details only when they are themselves a public contract.
Prefer behavior over method-shaped checklists. One operation can have several behaviors, and
one behavior can cross several operations. A test name and body should make clear:
- the scenario or input;
- the action or state transition;
- the observable outcome;
- why that outcome matters.
Name the scenario and expected result, not only the method under test. Keep separate behaviors
in separate tests so the test list documents the surface of the system and a failure points to
one behavior.
Keep cause and effect together
Use a visible setup → action → observation shape. Keep the data that causes the result close to
the assertion that observes it. A reader should not have to find a setup method or shared fixture
many lines or files away to understand why an assertion is true.
Helpers are useful when they hide irrelevant construction. They are harmful when they hide the
input, action, or expected result. Give helpers domain names that state their purpose and keep
the test's important facts at the call site.
Use an independent oracle
Expected values must come from an independent source: the specification, a worked example, a
known-good literal, or an independently trusted implementation. Do not recompute the expected
value with the same algorithm as the subject; two copies of the same mistake can pass together.
Choose inputs that can expose misuse:
- use non-default values when a default could make a broken implementation appear correct;
- use distinct values for distinct fields so accidental swapping or reuse is visible;
- cover meaningful empty, missing, null-like, boundary, invalid, and unusual values;
- vary inputs across cases when the behavior has distinct paths, without generating noise for
scenarios the product does not support.
Parameterize repeated scenarios only when each case remains clear and failures identify the
case. A table of opaque values is not a substitute for a readable behavior description.
Assert the contract, narrowly
Prefer the resulting state, return value, emitted event, persisted record, user-visible effect,
or error contract. State evidence usually tells more than proving which internal collaborator
was called. Test interactions only when the interaction is part of correctness: for example,
an external message must be sent exactly once, a transaction must be committed, or a protocol
requires a particular request.
When checking an interaction, assert only the arguments relevant to the behavior. Exact matches
for unrelated metadata, ordering, formatting, or call history create failures for changes that
do not affect the contract.
Keep assertions narrow enough that a failure explains what broke. Use a broad snapshot or whole
object comparison only when the whole representation is the contract. Test expected failures as
carefully as successful results: assert the error category and relevant details without coupling
the test to incidental wording or stack structure.
Keep tests complete, concise, and resilient
A good test has four qualities:
- Clarity: it reads like documentation of the public behavior.
- Completeness: its important inputs, action, and expected result are visible or named.
- Conciseness: irrelevant construction and unrelated assertions are out of the way.
- Resilience: it changes when the behavior changes, not when an implementation detail moves.
Make each test responsible for one logical behavior. Multiple steps are fine when they form one
user or protocol scenario; split unrelated outcomes. Avoid tests that merely mirror the current
call graph, verify private helpers, or change whenever a class is reorganized.
Build data that explains itself
Use the smallest data that proves the behavior. Keep mutable state fresh for each test and make
initialization and cleanup explicit. Factories, builders, and fixtures earn their place when they
remove irrelevant noise while preserving the important facts; they should not create magical
defaults that a reader cannot see.
Prefer readable, local data over forced reuse. Test code can tolerate a little repetition when
that repetition makes each example complete. If two cases look similar but represent different
business rules, keep them separate until their shared meaning is real.
Double only at boundaries
Prefer a real dependency when it is small, deterministic, and affordable. Use a local stand-in
when a real dependency is too slow, unavailable, destructive, or nondeterministic. A fake should
model the public contract and, when practical, be owned and maintained by the team that owns the
production implementation.
Use mocks for system boundaries such as external services, time, randomness, email, payment,
network, or other irreversible effects. Do not mock internal collaborators merely to reproduce
the current implementation. Do not mock a type or interface your code owns when a real instance
or simple fake can expose behavior more faithfully.
Design the boundary so it can be replaced cleanly: inject long-lived collaborators when an
object depends on them for its identity, and pass per-call work as method input. Use specific
operations at the boundary rather than a generic escape hatch that makes every test configure a
different protocol.
Make results deterministic
Control or isolate values the test does not own: clock, randomness, environment, filesystem,
network, process state, concurrency, and test data. Tests should not depend on execution order,
leftover state, wall-clock luck, or a particular machine.
Synchronize on an observable condition or completion signal. A fixed sleep guesses about timing;
it makes a test slow when the system is fast and flaky when the system is slow. Use timeouts to
bound waiting and report the condition that was not reached. Clean up resources even when the
test fails.
When a test fails intermittently, preserve the failure details, classify the source, and fix the
cause. Retries can hide a broken signal and do not make the test reliable.
Use the red → green loop when it fits
For a new behavior or bug fix, write one focused failing test at the agreed seam, implement only
enough behavior to pass it, then repeat for the next behavior. Refactor after the behavior is
green and keep the test contract stable. Do not write a large speculative test suite before the
implementation is understood.
Final test check
Before keeping a test, verify that:
- its name states the scenario and outcome;
- its seam is public and its behavior is in scope;
- cause and effect are easy to find;
- expected values are independent and inputs are discriminating;
- assertions describe the contract rather than incidental calls;
- data, doubles, time, and cleanup are controlled;
- failure output tells the next person where to look;
- the test is placed at the cheapest layer that still has enough fidelity.
For the source crawl and thematic notes behind this synthesis, read
the Google Testing Blog corpus notes.