Testing policy
Canonical testing and coverage policy for production behavior changes.
Intent
- Protect durable behavior, invariants, and contracts.
- Keep tests at the correct seam (unit vs integration vs scenario).
- Treat coverage as a non-regression guardrail, not a vanity target.
Required workflow for behavior changes
- Start with a failing test when introducing or changing durable behavior.
- If behavior is intentionally removed, delete stale tests instead of preserving the historical path.
- Implement the minimum change to pass.
- Refactor while keeping tests green.
Use the canonical Verification commands workflow:
make testduring normal implementationmake smokewhen runtime paths need quick feedbackmake checkbefore push, PR open, or PR update
Focused diagnostics remain available via make cover, make docs-check, and make cover-critical-domain when you need standalone coverage output separate from make check.
If a change is intentionally test-neutral (docs-only or no-behavior refactor), call that out explicitly in the PR.
Assertion policy
- Prefer positive assertions against durable contracts, outputs, and state transitions.
- Use negative assertions only for explicit invariants such as security, privacy, protocol framing, or mutual exclusion.
- Every negative assertion must include an adjacent
Invariant:rationale explaining why the absence matters. - Tombstone tests that only memorialize removed behavior are discouraged. If no durable invariant remains, delete the stale test.
CI non-regression gates
PR coverage compares against baselines published from main on badges:
- overall baseline:
coverage-baseline.txt - critical-domain baseline:
coverage-critical-domain-baseline.txt - critical package floors: ratcheted from
coverage-package-floors.json
PRs fail when coverage drops beyond configured tolerance.
Seed floor policy is versioned in:
docs/reference/coverage-floors.json
Testability requirements
Code that cannot be tested cannot be safely evolved. New code should follow these constructor and dependency rules:
- Accept dependencies at constructors; do not create hard dependencies inside logic paths.
- Define interfaces at consumption points.
- Use function injection for simple dependencies (clock/random/IO hooks).
- Keep production constructors thin; expose internal constructors for tests when needed.
- Keep fakes local to
*_test.goand implement only required methods.
What not to unit test
- Thin production wiring (
main, server composition glue) - Generated code (
api/gen,sqlc,temploutputs) - CLI flag plumbing when logic is already tested in invoked functions
Use integration/scenario tests for cross-package workflows and transport seams.
Generated code coverage exclusions
Coverage excludes generated sources via COVER_EXCLUDE_REGEX in Makefile. Update it when new generated outputs are introduced.