SuperAPI AI Directory

tdd

Testing & quality

View repository

Updated 10-16 07:24

About this skill

Test-Driven Development

Load the language standards skill first

Before writing the first test, load the standards skill for the language you are about to write. This skill owns the loop; the language skill owns the code. Loading this one does not satisfy the other, and being mid-task is not a reason to skip it.

Files you are about to touch Also load
*.go go-idiomatic
*.tsx *.jsx *.vue *.svelte frontend-best-practices
class= / className= / components/ui/ tailwind-shadcn

Philosophy

Examples in this skill use TypeScript. The principles are language-agnostic. For Go, test style (assertions, tables, doubles, helpers) comes from go-idiomatic § Testing, not from these examples.

Core principle: Tests should verify behavior through public interfaces, not implementation details. Code can change entirely; tests shouldn't.

Good tests are integration-style: they exercise real code paths through public APIs. They describe what the system does, not how it does it. A good test reads like a specification - "user can checkout with valid cart" tells you exactly what capability exists. These tests survive refactors because they don't care about internal structure.

Bad tests are coupled to implementation. They mock internal collaborators, test private methods, or verify through external means (like querying a database directly instead of using the interface). The warning sign: your test breaks when you refactor, but behavior hasn't changed. If you rename an internal function and tests fail, those tests were testing implementation, not behavior.

See tests.md for examples and mocking.md for mocking guidelines.

Anti-Pattern: Horizontal Slices

DO NOT write all tests first, then all implementation. This is "horizontal slicing" - treating RED as "write all tests" and GREEN as "write all code."

This produces crap tests:

  • Tests written in bulk test imagined behavior, not actual behavior
  • You end up testing the shape of things (data structures, function signatures) rather than user-facing behavior
  • Tests become insensitive to real changes - they pass when behavior breaks, fail when behavior is fine
  • You outrun your headlights, committing to test structure before understanding the implementation

Correct approach: Vertical slices via tracer bullets. One test → one implementation → repeat. Each test responds to what you learned from the previous cycle. Because you just wrote the code, you know exactly what behavior matters and how to verify it.

WRONG (horizontal):
  RED:   test1, test2, test3, test4, test5
  GREEN: impl1, impl2, impl3, impl4, impl5

RIGHT (vertical):
  RED→GREEN: test1→impl1
  RED→GREEN: test2→impl2
  RED→GREEN: test3→impl3
  ...

Workflow

1. Planning

When a contract exists (stubs merged into the feature branch by a scaffold PR), its signatures are frozen, because parallel tickets build against them. Skip the interface-design items below (confirming interface changes, deep modules, designing for testability) and write tests through the stub interfaces as they stand. If a test cannot be written without changing a signature, take the contract-change path in implement instead of editing it.

Before writing any code:

  • Confirm with user what interface changes are needed
  • Confirm with user which behaviors to test (prioritize)
  • Identify opportunities for deep modules (small interface, deep implementation)
  • Design interfaces for testability
  • List the behaviors to test (not implementation steps)
  • Get user approval on the plan

Ask: "What should the public interface look like? Which behaviors are most important to test?"

You can't test everything. Confirm with the user exactly which behaviors matter most. Focus testing effort on critical paths and complex logic, not every possible edge case.

When there is no user to ask

A spawned executor working a ticket has nobody to answer these questions. Do not stall waiting, and do not invent scope. Substitute the written sources instead:

  • Interface changes — the ticket's "What to build", then the source plan's technical design decisions. A named type, route or module boundary there is settled; implement it rather than reopening it.
  • Which behaviours to test — the ticket's acceptance criteria. They are already the prioritised list this section would ask the user for. Test each one, and stop there.
  • Plan approval — unavailable. Proceed without it and make the plan reviewable instead.

If a question survives all three, the ticket has a real gap. Take the reading that keeps the slice narrowest, write the assumption into the PR body under a heading the reviewer will actually see, then carry on. A frozen signature is never an assumption to record; that is a contract change.

An executor that stops to ask blocks the fleet behind it; one that quietly widens scope ships a diff nobody asked for. Recording the assumption is what makes the third option safe.

Never tick an acceptance criterion you could not verify. Name it in the PR body and say what stopped you.

The Iron Law

NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST.
  • Write code before test? Delete it. Start over. No keeping it as "reference."
  • After writing the test, run it. Watch it fail. Confirm the failure is because the feature is missing — not a typo, wrong import, or test mistake. If you didn't watch it fail, you don't know if it tests the right thing.
  • Test passes immediately? You're testing existing behaviour. Fix the test.
  • Never refactor while RED. Get to GREEN first.

2. Tracer Bullet

Write ONE test that confirms ONE thing about the system:

RED:   Write test for first behavior → test fails
GREEN: Write minimal code to pass → test passes

This is your tracer bullet - proves the path works end-to-end.

3. Incremental Loop

For each remaining behavior:

RED:   Write next test → fails
GREEN: Minimal code to pass → passes

Apply the checklist below after each cycle.

4. Refactor

After all tests pass, look for refactor candidates:

  • Extract duplication
  • Deepen modules (move complexity behind simple interfaces)
  • Apply SOLID principles where natural
  • Consider what new code reveals about existing code
  • Run tests after each refactor step
  • Leave frozen contract signatures unchanged## Checklist Per Cycle
[ ] Test describes behavior, not implementation
[ ] Test uses public interface only
[ ] Test would survive internal refactor
[ ] Code is minimal for this test
[ ] No speculative features added