Skip to content
xiv

test-writing

Adversarial test design: enumerate scenarios from the spec before reading the implementation.

The skill's loyalty is to the specified behavior — the issue, spec, types, or user intent — never to the implementation. A test that re-asserts what the code currently does certifies bugs as intended behavior. Everything in the skill serves one question, asked of every test:

Could this test pass while the behavior it guards is broken?

If yes, redesign it. It triggers whenever tests are being written or extended — new feature coverage, a regression test for a bugfix, backfilling an untested module, E2E journeys — even when the request is just "add tests" or an implementation task that implies them.

The three-step design process

  1. Derive the contract before reading the implementation. Read the spec first and write down what the behavior must do; only then read the code. Reading the code first anchors you, and its bugs become "expected values" in assertions. Where spec and code disagree, that's a finding, not a test fixture.
  2. Enumerate scenarios exhaustively across fixed buckets — happy paths (usually more than one), error paths (asserting how it fails, not just "throws"), boundaries (empty/one/many, zero/negative/max, unicode, null at every optional seam), state transitions and persistence, idempotency and replay, authZ/privacy (the wrong user, the other tenant), and concurrency/async timing. Skipping a bucket must be a decision, not an oversight, and every kept scenario must name the realistic regression it would catch — otherwise it's cut as ballast.
  3. Route each scenario to the lowest level that can truly falsify it — but never to a level that can't observe the claim (a mocked-DB "integration" test cannot falsify a persistence claim).

Per-level reference docs

LevelBelongs thereReference
unitpure logic, branching, boundary math, error mapping — cheap, so exhaustivereferences/unit.md: contracts not internals, mandatory boundary analysis, table-driven discipline, mock only true seams, property-based tests for invariants
componentone UI component, rendered for realreferences/component.md: drive with real user events, query the accessibility tree, cover the full state matrix, exact callback payloads, Storybook stories as fixtures
integrationreal collaborators composed at a seamreferences/integration.md: real DB/queue by default, enter through the public entrypoint, assert the exact persisted shape, transactionality/idempotency/concurrency/authZ scenarios, parity tests for replacement work
e2efull stack, real user journeysreferences/e2e.md: journeys not pages, supported product entrypoints only, real async workers, assertions that detect wrong data, oracle vs. rigging separation

Rules that hold at every level

  • Prove every test can fail. A bugfix regression test runs red on pre-fix code first; new coverage gets the behavior temporarily broken to watch the test fail for the right reason, then reverted. A test never seen red is unverified.
  • Fail-closed. A well-designed test failing on a real bug is doing its job — never weaken an assertion, broaden a matcher, or add a retry to reach green.
  • Assertions are exact, scoped, and semantic — the values a user would see or the exact persisted shape, not presence-of-some-text or toBeTruthy().
  • Don't bypass the behavior under test in setup. Seeding prerequisite state covered elsewhere is fine; short-circuiting the tested path is not.
  • Determinism and isolation — control clock, randomness, timezone, network; no arbitrary sleeps; every test owns its data and passes in any order.
  • Match the repo — discover existing frameworks, helpers, and factories before inventing new ones.

Judgment calls and suspected bugs

The skill expects visible judgment: a question queue accumulates ambiguities and skipped scenarios instead of interrupting per item — interrupting mid-task only when the answer would redirect the remaining work. When a test reveals a suspected bug, the test is never bent to match the implementation: a clear spec violation stays fail-closed (per the repo's known-failing convention) and is reported prominently; an unclear bug-or-intended case is written to the best reading and queued.

The teach-back

Every run ends with a fixed structure the human can audit in two minutes: what was tested and why, a coverage map (scenario × level × test × regression caught), the judgment calls made (vetoable), suspected bugs with expected-vs-actual, and numbered one-line questions. Before writing it, the skill requires a self-spot-check against test-audit's per-test questions — fixing what that finds and noting what it couldn't.

Relation to xiv

This skill isn't wired into a CLI command; it's the standard any agent in this ecosystem applies when producing tests — including work done inside xiv implement runs, whose validation step is what those tests must honestly gate. Its audit counterpart is test-audit.