maintainable-tests
Installation
SKILL.md
Maintainable tests
Passive: apply silently to any test work, and for small edits mention only the decisions that affect the result. Match the repository's framework and style before importing a pattern, and reuse adequate existing coverage instead of duplicating it.
A developer returning in six months should learn the behaviour, its edge cases and why each important test exists from the tests alone.
Rules
- Name tests after the behaviour and outcome in domain language (
rejects withdrawals that exceed the current balance), not the function (returns false from validateInvite). Parameterised rows get names that say why the row exists. - Use concrete, domain-shaped values. Keep intent local: no distant factory or hidden global setup needed to understand a test. When readability and DRY conflict, choose readable duplication; extract a helper only when its name keeps domain meaning.
- One behaviour per test, with every assertion needed to prove it (result, state change, emitted event).
- Pick the least powerful double that proves the rule: stub for queries, fake for simple state, spy for an important effect, mock only when the interaction is the behaviour. Mock-heavy tests usually freeze implementation, not behaviour; back adapters with a contract or integration check where they can drift.
- Make tests deterministic: control time, randomness, locale, timezone, concurrency, persistence at clear boundaries.
- Hard-to-test code: use existing safe seams first (framework overrides, scoped and reliably restored patches). Refactor production only when necessary, proportionate and authorised; in tests-only work, propose the change instead of making it. Never expose private internals or widen an interface just to make a test pass. Painful tests are often design feedback.
- Legacy and compatibility behaviour needs a one-line rationale (customer contract, migration, bug reference, or "unknown"). Reuse existing coverage before adding characterisation tests, and change legacy production code in small verified steps.
- A regression test must fail for the bug it guards. Run it against the defect when feasible.
- Review order: tests that can pass while behaviour is broken, then brittle implementation coupling, then missing edge cases, then style.