writing-unit-tests
Installation
SKILL.md
Writing unit tests
REQUIRED BACKGROUND: the principal-engineering skill. testing-changes governs which tests a change owes; this skill is the craft of the tests themselves.
Overview
A unit test is a behavioral claim with a name, read by the next engineer during a red build. Core principle: test the contract, not the implementation. The name carries the claim, and the test stays simple enough that it cannot itself be wrong.
Contract over implementation
- Test through the public contract of the unit. A refactor that preserves behavior should not break tests; when it does, the tests were asserting the implementation, and they now punish improvement.
- Do not assert call sequences, internal state, or that method A called method B, unless the interaction IS the contract (a required side effect on a boundary). Asserting internals tests the implementation twice and the behavior zero times.
- Never derive the expected value from the production arithmetic, neither by reimplementing the formula nor by invoking the shared helper that computes it. Both prove the code equals itself, and both stay green when the shared code carries the bug. Expected values are literals worked out independently (by hand, from a spec, from real data), with the derivation in a comment.
One behavior per test, named as the claim
- One behavior per test; splitting is cheaper than archaeology on a multi-assert failure.
- The name states subject, scenario, and expected outcome:
expired_token_is_rejected_with_401, nottest_auth_3. Test names describe behavior, state transitions, and invariants; never delivery order, ticket keys, or phases. Read the test list of a module and you have read its spec. - Arrange, act, assert, visibly and in that order. No branching, loops, or logic in a test: a test with logic needs its own test. Shared setup earns a builder or a role-named fixture; a mystery blob fixture hides which arranged fact the assertion depends on. Generation and iteration live in builders and helpers, not in the test body. Property-based tests are the accepted form for invariants and follow their framework's shape. Example-based tests stay logic-free.