write-tests
Tests that fail when behaviour breaks, rather than restating the implementation back at itself.
Save as .claude/skills/write-tests/SKILL.md in a repository, or under ~/.claude/skills/write-tests/SKILL.md to have it everywhere. The copy button takes the whole file, frontmatter included.
--- name: write-tests description: Write tests that catch real breakage instead of restating the implementation. Use when asked to add tests, improve coverage, write unit or integration tests, or test a new function or endpoint. --- # Write tests A test earns its place by failing when the behaviour breaks. A test that passes whatever the code does is worse than none, because it costs maintenance and buys confidence that is not real. ## Before writing anything Ask what this code is supposed to guarantee. Test that, not the lines. If you cannot state the guarantee in a sentence, the test you are about to write will assert the implementation back at itself. ## What to test, in order 1. **The happy path**, once. Not five variations of it. 2. **The boundaries.** Empty, one, many, the maximum, zero, negative, the first and last element. Most bugs are here. 3. **The error paths.** Invalid input, a missing dependency, a timeout. Assert the specific failure, not merely that something was raised. 4. **The bug you just fixed.** Every fix gets a test that fails without it. This is the highest value test in any suite because you know it catches a real defect. ## What not to test - Code you do not own. Testing that the standard library sorts is not a test of your code. - Getters, setters and pass through wrappers with no logic. - The same logic at three levels. Pick the level where a break would matter. - Implementation details: private methods, call counts, the order of internal steps. These fail on every refactor and catch nothing. Coverage is a diagnostic, not a target. Chasing a number produces tests for the code that was easiest to reach, which is rarely the code most likely to break. ## The rules that keep a suite usable **One reason to fail.** A test that asserts six unrelated things tells you little when it goes red. **Name it after the behaviour.** `rejects_an_expired_token` tells you what broke from the test report alone. `test_auth_2` does not. **No logic in the test.** No loops, no branches, no computing the expected value with the same function under test. Write the expected value literally, even when repetitive. A test with a bug in it is undetectable. **Deterministic.** No real clock, no real network, no random without a fixed seed, no dependence on test order. An intermittent test gets ignored, then the whole suite does. **Arrange, act, assert**, visibly separated. A reader should see the setup, the one call, and the check. ## Mocking Mock at the boundary: the network, the clock, the filesystem, the payment provider. Do not mock the thing you are testing, and do not mock your own internal functions to make a test pass, because then you are testing the mock. A test with five mocks is usually telling you the code has too many dependencies. That is a finding worth reporting, not working around. ## Verify the test itself Before you call it done, break the code deliberately and confirm the test fails. A test that has never been seen to fail has not been shown to work. ## Report back 1. What guarantee each test protects. 2. Anything you chose not to test, and why. 3. Anything untestable as written, with the change that would fix it.
what it needs
Nothing. This skill is instructions only, so it works in any agent that reads SKILL.md with nothing installed and no account. Save the file and it is live in the next session.
the instructions, in full
Write tests
A test earns its place by failing when the behaviour breaks. A test that passes whatever the code does is worse than none, because it costs maintenance and buys confidence that is not real.
Before writing anything
Ask what this code is supposed to guarantee. Test that, not the lines.
If you cannot state the guarantee in a sentence, the test you are about to write will assert the implementation back at itself.
What to test, in order
- The happy path, once. Not five variations of it.
- The boundaries. Empty, one, many, the maximum, zero, negative, the first and last element. Most bugs are here.
- The error paths. Invalid input, a missing dependency, a timeout. Assert the specific failure, not merely that something was raised.
- The bug you just fixed. Every fix gets a test that fails without it. This is the highest value test in any suite because you know it catches a real defect.
What not to test
- Code you do not own. Testing that the standard library sorts is not a test of your code.
- Getters, setters and pass through wrappers with no logic.
- The same logic at three levels. Pick the level where a break would matter.
- Implementation details: private methods, call counts, the order of internal steps. These fail on every refactor and catch nothing.
Coverage is a diagnostic, not a target. Chasing a number produces tests for the code that was easiest to reach, which is rarely the code most likely to break.
The rules that keep a suite usable
One reason to fail. A test that asserts six unrelated things tells you little when it goes red.
Name it after the behaviour. rejects_an_expired_token tells you what
broke from the test report alone. test_auth_2 does not.
No logic in the test. No loops, no branches, no computing the expected value with the same function under test. Write the expected value literally, even when repetitive. A test with a bug in it is undetectable.
Deterministic. No real clock, no real network, no random without a fixed seed, no dependence on test order. An intermittent test gets ignored, then the whole suite does.
Arrange, act, assert, visibly separated. A reader should see the setup, the one call, and the check.
Mocking
Mock at the boundary: the network, the clock, the filesystem, the payment provider. Do not mock the thing you are testing, and do not mock your own internal functions to make a test pass, because then you are testing the mock.
A test with five mocks is usually telling you the code has too many dependencies. That is a finding worth reporting, not working around.
Verify the test itself
Before you call it done, break the code deliberately and confirm the test fails. A test that has never been seen to fail has not been shown to work.
Report back
- What guarantee each test protects.
- Anything you chose not to test, and why.
- Anything untestable as written, with the change that would fix it.