| name | test-principles |
| description | Principles for writing high-quality automated tests, based on Kent Beck, Kent C. Dodds, and t-wada. This skill MUST be used whenever writing or modifying implementation code โ tests are part of implementation, not a separate step. Also use when writing tests, asked to "write tests", "add tests", "test this", reviewing or auditing existing tests, "review tests", "check test quality", "ใในใใๆธใใฆ", or "ใในใๅๅ". If you are implementing a feature or fixing a bug, this skill applies. |
Automated Test Principles
HOW to write tests (verification), not WHAT to test (validation). Which behaviors to cover is a human decision.
Tests Are Part of Implementation
When implementing features or fixing bugs, write tests in the same change. Red โ Green โ Refactor:
- Write a failing test that describes the desired behavior
- Write the minimum implementation that passes
- Refactor with the behavior guarded
- Add edge cases in a separate section
Interleave tests with implementation. Tests bolted on afterward tend to verify what was built, not what the code should do.
If the user asks only for implementation without mentioning tests, still write them. Say so briefly: "Adding tests alongside the implementation to keep them behavior-focused."
Assert the Result, Not the Process
Assert observable behavior, not internal mechanics.
it('should call reduce internally', () => {
const spy = vi.spyOn(items, 'reduce');
applyDiscount(items, { type: 'fixed', value: 10 });
expect(spy).toHaveBeenCalled();
});
it('fixed $10 discount on $100 item yields $90', () => {
const items = [{ name: 'Shirt', price: 100, quantity: 1 }];
expect(applyDiscount(items, { type: 'fixed', value: 10 })).toBe(90);
});
Prefer integration tests when behavior crosses module boundaries. Use unit tests for isolated domain rules. Do not test implementation details in either.
Name Tests as Mini-Specifications
Test names state the scenario and expected result.
test('parses key=value pairs', () => { ... });
test('decodes URI-encoded characters', () => { ... });
test('?foo=1 returns { foo: "1" }', () => { ... });
test('%E5%BA%83 in value decodes to ๅบ', () => { ... });
test('key without = maps to empty string', () => { ... });
'handles X' or 'processes Y' describes the implementation, not the behavior.
Make Failures Self-Diagnosing
expect(result === 160).toBe(true);
expect(result).toBeTruthy();
expect(result).toBe(160);
Reserve toBeTruthy for boolean contracts.
Separate Edge Cases Visually
Group primary behavior and edge cases separately. Boundary tests should be easy to find without scanning the happy path.
Constraints
- Fast: eliminate slow dependencies when the behavior under test does not cross that boundary.
- Deterministic: a flaky test erodes trust in the entire suite. No time-dependent logic, unseeded randomness, or shared mutable state.
- Independent: each test produces the same result regardless of execution order.
- Not the only safety net: complement automated tests with real browser testing, monitoring, and manual QA.
Review Checklist
Each test:
What's missing:
Flag issues with a concrete fix. For missing coverage, suggest specific test cases with expected values.