40%+ of CodeRabbit review issues across all AGH PRs are test-shape violations. The CLAUDE.md rules are correct but agents keep ignoring them when adding "just one more case." This skill is the Go test-shape gate after consolidate-test-suites has justified the invariant, owning layer, and canonical suite.
Procedures
Step 0: Confirm Placement
- Use
consolidate-test-suitesbefore creating a new Go test file, moving coverage, or adding tests primarily for a task checklist or coverage target. - Record the invariant, owning layer, and canonical suite before applying Go-specific shape rules.
- If no invariant or owning layer exists, stop. Do not add a Go test just to raise coverage.
Step 1: Identify the Edit Surface
- Determine whether the edit creates a new test file, adds cases to an existing test, or refactors an existing test.
- Read the existing file (if any) so the new cases match the established subtest pattern.
- Read
references/test-shape-rules.mdfor the canonical rule list.
Step 2: Apply Subtest Discipline
- Every test case lives inside
t.Run("Should ...", func(t *testing.T) { ... }). The"Should ..."prefix is mandatory. - Adding inline cases to an existing function is a blocking violation — refactor the function so each case is its own subtest.
- Table-driven layout is the default. Each row carries a
namefield used as the subtest name. - Helpers used inside the test call
t.Helper().
Step 3: Apply Parallelism Discipline
- Default: every independent subtest calls
t.Parallel(). - Single legitimate opt-out: tests that use
t.Setenv(directly or transitively) MUST NOT callt.Parallel(). Go's testing contract forbids this combination. - When a reviewer suggests adding
t.Parallel()to a test that usest.Setenv, mark the suggestion INVALID with rationale and citedocs/_memory/lessons/L-002-tparallel-vs-tsetenv.md. - Tests on shared mutable state (file-system, ports, package-global maps) opt out with a comment explaining the dependency.
Step 4: Apply Error-Handling Discipline
- Never use
_ = errFn(...)in tests. Handle every error explicitly. json.Marshal,json.Unmarshal,Close, file operations — every error getsif err != nil { t.Fatalf(...) }or equivalent.- Test cleanup paths run via
t.Cleanup(...)and handle their own errors.
Step 5: Strengthen Assertions
- Status-code-only assertions are insufficient. Always assert response body, error message, or contract-specific evidence (idempotency key, request payload, persisted state).
- For idempotency tests: explicitly re-use the same idempotency key on the second call and verify the contract honors it (don't pass empty body — that just proves re-entry, not idempotency).
- Deterministic time: replace
time.Now()with injected clocks; deterministic IDs use injected ID generators. - For new types satisfying interfaces, ensure
var _ Interface = (*Type)(nil)exists in production code (not in the test file).
Step 6: Apply Integration / E2E Discipline
- Integration tests live in
*_integration_test.gowith//go:build integrationat the top. Co-locate them with the package they test — never in a separatetest/directory. make testruns unit only.make test-integrationadds the+integrationbuild tag.make test-e2e-runtimeis the daemon-side Go harness;make test-e2e-webis the browser-side Playwright harness.- Use
TestMainfor expensive one-time setup/teardown. - Use real dependencies — real SQLite via
t.TempDir(), mock ACP server as a subprocess (acpmock). Avoid in-process fakes when a real subprocess can be wired. - Keep integration tests fast enough for CI: ~30s max per package.
- E2E tests are part of the runtime contract. When a runtime contract changes (prompt augmenter, situation context, fixture format), the E2E mock and matchers ship in the same PR. Otherwise tests pass against a stale prompt and fail later.
- Read
references/test-shape-rules.md"Integration / E2E" section for additional patterns.
Step 7: Pre-Commit Validation
- Run
python3 scripts/check-test-conventions.py <file_path>to scan the test file for violations. The script is a regex-based fast check; it complementsmake verify. - If the script reports violations, fix them before running
make verify. - After edits, run
go test ./<package> -count=1 -racefor the affected package, thenmake verify. make verifyis the commit gate. If verification is blocked by an external/branch-side asset issue (missing test fixture, etc.), do NOT commit — report the verified blocker and hold.- Test failures are production bugs. Fix production code; do not weaken assertions. The only legitimate exception is documenting an INVALID review item with concrete evidence.
Error Handling
- Existing file uses non-
Shouldnaming throughout: do not mix conventions. Ask whether to refactor the file or to add a same-style case (older files may predate the rule). Default: refactor. scripts/check-test-conventions.pyreturns false positives: the script is heuristic; a comment justifying the deviation is acceptable when the false positive is real (e.g., test data named "Should" by coincidence).t.Setenvused inside a helper that callers cannot inspect: read the helper transitively. If env mutation occurs anywhere in the call graph, the entire test stays serial.- Race-enabled tests touching cgo: the
go test -raceinvocation must run withCGO_ENABLED=1. The repository'srunRaceEnabledGoCommandhelper handles this. Do not trust ambient env.