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
- 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.
- 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.
- 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
| Level | Belongs there | Reference |
|---|---|---|
| unit | pure logic, branching, boundary math, error mapping — cheap, so exhaustive | references/unit.md: contracts not internals, mandatory boundary analysis, table-driven discipline, mock only true seams, property-based tests for invariants |
| component | one UI component, rendered for real | references/component.md: drive with real user events, query the accessibility tree, cover the full state matrix, exact callback payloads, Storybook stories as fixtures |
| integration | real collaborators composed at a seam | references/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 |
| e2e | full stack, real user journeys | references/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.