test-first · git:20260927.b407231 · 2026-09-27 · sha256 65da3357eaca7d26
test-first git:20260927.b407231A
Immutable. This exact content never changes and is served at /api/v1/blob/65da3357eaca7d26.
--- name: test-first description: The test-SHAPING technique applied inside the test-first protocol — pick the lowest test level that exercises the behavior (unit, integration, contract, e2e, golden, property-based, evaluation), name the test after the spec contract, and assert one outcome per test. The workflow itself (entry conditions, RED-GREEN-REFACTOR-COMMIT gates, variants, exceptions) is the test-first protocol. id: skill.test-first tier: 2 kind: skill origin: seed title: 'test-first — shape each test: lowest level, contract-named, one outcome' owns: - test-first.shaping - test-first.level-selection - test-first.lean-suite - test-first.no-lint-only-tests requires: - protocol.test-first peers: - skill.spec-author load_when: - "shape a new test" - "pick a test level" - "name a test after a spec contract" - "unit vs integration vs e2e choice" - "one outcome per test" - "consolidate or shrink a test suite, duplicate or expensive tests" - "project-specific test, generic or synthetic fixture" - "coverage lint wants a test for a contract" artifacts: - templates/spec.template.md prevents: Tests written at the highest level that happens to work, named after the function rather than the contract, asserting several outcomes at once so a failure names no cause. est_tokens: 1286 --- # test-first (the test-shaping technique) The cycle — RED → GREEN → REFACTOR → COMMIT, its per-phase gates, the characterize-first rule, the bug-fix and refactor variants, the migration safety gate, and the recorded exceptions — lives in `docs/graph/protocols/test-first.md`, the one home for the workflow. This skill owns the craft of shaping each test the cycle asks for. ## Test level selection Pick the lowest level that exercises the behavior. | Level | Use when | |-------------------|---------------------------------------------------------| | Unit | Pure logic, transformations, parsers, validators. | | Integration | Crossing an adapter (DB, file, network, SDK, model). | | Contract | API endpoints, structured outputs, message schemas. | | End-to-end | Critical flows — one or two per flow, no more. | | Golden / snapshot | Deterministic transforms, prompts, renderers. | | Property-based | Algorithms where the property is clearer than examples. | | Evaluation | LLM/VLM behavior, with rubrics and pass thresholds. | ## Test shape - **The name names the contract.** The spec §4 contract slug, transformed for the language's convention (`test_submit_valid_form_returns_2xx`, `submitValidFormReturns2xx`, …). The reviewer should be able to read the test names and reconstruct the spec. A name that claims more than its body asserts is worse than an empty assertion: the standard way to ask "is this covered?" is to search test names, and a false name answers yes. - **The body is Given/When/Then.** Set up Given, perform When, assert Then. - **One outcome per test, or per named sub-case** (or one assertion group about the same outcome), so a failure names its cause. When several outcomes follow from one expensive action, the action runs once and each outcome is asserted as its own named sub-case; what must not happen is one giant test whose first failure hides the rest. - **Reusable code is tested over its consumers, not for one of them.** When the code under test serves many consumers (plugins, packs, tenants, configurations, projects), the default test checks a property over every consumer on disk, or checks the behavior over one generic, synthetic fixture consumer (never production data, kernel §4). No test class is named for a real consumer, and no test asserts a real consumer's literal values: such a test pins the shared code to one customer and breaks when that customer changes. ## No test only to turn a lint green (`test-first.no-lint-only-tests`) A coverage lint can report a contract that no test names. Never answer it with a test that asserts nothing new just to turn the lint green. If an existing test already asserts the contract, cite the contract's slug in that test. If no test asserts it, the gap is real, and the contract gets the test it needs. ## Keeping the suite lean (`test-first.lean-suite`) Every increment adds its own tests, so a suite only grows unless someone shrinks it on purpose. Duplicates and repeated expensive setup pile up unseen until the whole suite is measured. - **Consolidate by action, not by safe fold.** Where several tests pay for the same expensive action (a repository copy, a fresh environment, a full gate run), run the action once and assert each outcome under its own name. Per-stage happy-path tests that one end-to-end chain run already covers go; so do tests written for one real consumer (above) and tests that assert nothing a sibling does not. Merging only the folds that are obviously safe lowers the count and leaves the cost where it was. - **A size target is an aspiration, never a quota.** A number chosen up front is a direction, not something to delete toward. - **Every deleted test names its survivor**: the test that still fails when the deleted test would have (the one that still kills its mutants). With no survivor, the deletion is a coverage loss and is recorded as one. A reduction pass that ends with more tests than it started with owes an explanation for each addition. - **Consolidation is planned work.** Schedule it once the spec's behavior has landed, as its own increment in the plan-of-record (`protocol.grill` owns increment order). Run mid-spec, beside the critical path, it competes with the work it should follow. - **Review for the smells that inflate a suite**: byte-identical test bodies, several tests calling the same expensive helper with the same arguments, and expensive per-test setup. The seed's tool corpus catalogs a lint that flags them, `tool-corpus/testing/test-hygiene-lint.md`. ## Reference files - `docs/graph/protocols/test-first.md` — the workflow this technique serves. - `docs/graph/templates/spec.template.md` — where contracts live (§4) and test mapping is recorded (§10). - `docs/graph/agents/04-tester.md` — the agent that owns the cycle. - `docs/graph/agents/02-implementer.md` — the agent that writes GREEN.