Skill Tracer
Skills › gstack › review › testing.md

testing.md

160 lines · review/specialists/testing.md at 3f79d81

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# Testing Specialist Review Checklist
2
3Scope: Always-on (every review)
4Output: JSON objects, one finding per line. Schema:
5{"severity":"CRITICAL|INFORMATIONAL","confidence":N,"path":"file","line":N,"category":"testing","summary":"...","fix":"...","fingerprint":"path:line:testing","specialist":"testing"}
6Optional: line, fix, fingerprint, evidence, test_stub.
7If no findings: output `NO FINDINGS` and nothing else.
8
9If the caller explicitly asks for an ASCII coverage diagram, first read the
10named source and test files directly in a dedicated tool call. Keep that tool
11call limited to those two file reads, using either native Read calls or a simple
12shell display such as `cat -n src/file && echo ---- && cat -n test/file`. Read
13diffs, package files, configs, or other context in separate tool calls. Then
14output the diagram before any JSON findings. Use one function root per public or
15changed function, put the `[OK]` or `[GAP]` marker on the same branch row as the
16tested or missing path, and keep the legend inside the same diagram block:
18```text
19src/billing.ts
20processPayment(amount, currency)
21├── valid USD happy path returns success [OK]
22└── invalid amount / unsupported currency branches [GAP]
23refundPayment(paymentId, reason)
24└── refund success and guard branches not imported or untested [GAP]
25Legend: [OK] tested [GAP] no test
26```
28Do not rely on a summary table, prose paragraph, or distant nested marker as the
29only coverage evidence. Covered happy-path rows must name the successful, valid,
30or concrete tested input path; gap rows must stay under the function that owns
31the missing path.
33---

Testing Specialist Review Checklist

Scope: Always-on (every review) Output: JSON objects, one finding per line. Schema: {"severity":"CRITICAL|INFORMATIONAL","confidence":N,"path":"file","line":N,"category":"testing","summary":"...","fix":"...","fingerprint":"path:line:testing","specialist":"testing"} Optional: line, fix, fingerprint, evidence, test_stub. If no findings: output NO FINDINGS and nothing else.

If the caller explicitly asks for an ASCII coverage diagram, first read the named source and test files directly in a dedicated tool call. Keep that tool call limited to those two file reads, using either native Read calls or a simple shell display such as cat -n src/file && echo ---- && cat -n test/file. Read diffs, package files, configs, or other context in separate tool calls. Then output the diagram before any JSON findings. Use one function root per public or changed function, put the [OK] or [GAP] marker on the same branch row as the tested or missing path, and keep the legend inside the same diagram block:

src/billing.ts
processPayment(amount, currency)
├── valid USD happy path returns success [OK]
└── invalid amount / unsupported currency branches [GAP]
refundPayment(paymentId, reason)
└── refund success and guard branches not imported or untested [GAP]
Legend: [OK] tested [GAP] no test

Do not rely on a summary table, prose paragraph, or distant nested marker as the only coverage evidence. Covered happy-path rows must name the successful, valid, or concrete tested input path; gap rows must stay under the function that owns the missing path.


Raw lines 1 to 34
35## Categories
37### Exploratory hypotheses
39The parent runs one shared exploratory QA pass before Fix-First, including small diffs
40that skip specialists. Supply high-risk changed contracts, adverse scenarios and native
41test candidates to that pass; do not launch another explorer, edit product/tests or
42commit. A test proposal uses `test_stub` and keeps the caller's approval requirements.
43Check real request/queue/storage effects, retries, duplicates and interrupted recovery
44where applicable. A diagram or test count is not executed proof. For discoveries promoted
45to regressions, require failure for the reproduced bug before repair and green plus
46original/adjacent probes afterward; never accept buggy-output goldens or discarded valid
47red tests. Unit tests suit logic; real integration/E2E tests protect boundaries mocks hide.

Exploratory hypotheses

The parent runs one shared exploratory QA pass before Fix-First, including small diffs that skip specialists. Supply high-risk changed contracts, adverse scenarios and native test candidates to that pass; do not launch another explorer, edit product/tests or commit. A test proposal uses test_stub and keeps the caller's approval requirements. Check real request/queue/storage effects, retries, duplicates and interrupted recovery where applicable. A diagram or test count is not executed proof. For discoveries promoted to regressions, require failure for the reproduced bug before repair and green plus original/adjacent probes afterward; never accept buggy-output goldens or discarded valid red tests. Unit tests suit logic; real integration/E2E tests protect boundaries mocks hide.

Raw lines 37 to 48
49### Missing Negative-Path Tests
50- New code paths that handle errors, rejections, or invalid input with NO corresponding test
51- Guard clauses and early returns that are untested
52- Error branches in try/catch, rescue, or error boundaries with no failure-path test
53- Permission/auth checks that are asserted in code but never tested for the "denied" case

Missing Negative-Path Tests

  • New code paths that handle errors, rejections, or invalid input with NO corresponding test
  • Guard clauses and early returns that are untested
  • Error branches in try/catch, rescue, or error boundaries with no failure-path test
  • Permission/auth checks that are asserted in code but never tested for the "denied" case
Raw lines 49 to 54
55### Missing Edge-Case Coverage
56- Boundary values: zero, negative, max-int, empty string, empty array, nil/null/undefined
57- Single-element collections (off-by-one on loops)
58- Unicode and special characters in user-facing inputs
59- Concurrent access patterns with no race-condition test

Missing Edge-Case Coverage

  • Boundary values: zero, negative, max-int, empty string, empty array, nil/null/undefined
  • Single-element collections (off-by-one on loops)
  • Unicode and special characters in user-facing inputs
  • Concurrent access patterns with no race-condition test
Raw lines 55 to 60
61### Negative Assertions
62- A new assertion that something is absent (`not.toContain`, `not in`, `assertNotIn`,
63 `pytest.raises` on a call that used to succeed) records a behavior removal, not coverage.
64 Cite the spec line, plan item or PR text that authorizes the removal; with none, report it
65 as a product decision shipped as a test.
66- A rename or removal in this diff can empty a standing negative assertion: a test asserting
67 the old token is absent now passes forever and guards nothing. For each identifier, flag,
68 heading or string the diff renames or deletes, grep the test suite for negative assertions
69 naming the old value, including inside tests that assert something else, and report each
70 as a coverage gap whose fix names the new value.

Negative Assertions

  • A new assertion that something is absent (not.toContain, not in, assertNotIn, pytest.raises on a call that used to succeed) records a behavior removal, not coverage. Cite the spec line, plan item or PR text that authorizes the removal; with none, report it as a product decision shipped as a test.
  • A rename or removal in this diff can empty a standing negative assertion: a test asserting the old token is absent now passes forever and guards nothing. For each identifier, flag, heading or string the diff renames or deletes, grep the test suite for negative assertions naming the old value, including inside tests that assert something else, and report each as a coverage gap whose fix names the new value.
Raw lines 61 to 71
72### Test Isolation Violations
73- Tests sharing mutable state (class variables, global singletons, DB records not cleaned up)
74- Order-dependent tests (pass in sequence, fail when randomized)
75- Tests that depend on system clock, timezone, or locale
76- Tests that make real network calls instead of using stubs/mocks

Test Isolation Violations

  • Tests sharing mutable state (class variables, global singletons, DB records not cleaned up)
  • Order-dependent tests (pass in sequence, fail when randomized)
  • Tests that depend on system clock, timezone, or locale
  • Tests that make real network calls instead of using stubs/mocks
Raw lines 72 to 77
78### Flaky Test Patterns
79- Timing-dependent assertions (sleep, setTimeout, waitFor with tight timeouts)
80- Assertions on ordering of unordered results (hash keys, Set iteration, async resolution order)
81- Tests that depend on external services (APIs, databases) without fallback
82- Randomized test data without seed control

Flaky Test Patterns

  • Timing-dependent assertions (sleep, setTimeout, waitFor with tight timeouts)
  • Assertions on ordering of unordered results (hash keys, Set iteration, async resolution order)
  • Tests that depend on external services (APIs, databases) without fallback
  • Randomized test data without seed control
Raw lines 78 to 83
84### Security Enforcement Tests Missing
85- Auth/authz checks in controllers with no test for the "unauthorized" case
86- Rate limiting logic with no test proving it actually blocks
87- Input sanitization with no test for malicious input
88- CSRF/CORS configuration with no integration test

Security Enforcement Tests Missing

  • Auth/authz checks in controllers with no test for the "unauthorized" case
  • Rate limiting logic with no test proving it actually blocks
  • Input sanitization with no test for malicious input
  • CSRF/CORS configuration with no integration test
Raw lines 84 to 89
90### Coverage Gaps
91- New public methods/functions with zero test coverage
92- Changed methods where existing tests only cover the old behavior, not the new branch
93- Utility functions called from multiple places but tested only indirectly
94- A path whose only tests are weak (★ smoke/existence/trivial, or a new test failing the
95 authoring gate below) stays a coverage gap at its existing severity; a low-value test
96 never closes a gap

Coverage Gaps

  • New public methods/functions with zero test coverage
  • Changed methods where existing tests only cover the old behavior, not the new branch
  • Utility functions called from multiple places but tested only indirectly
  • A path whose only tests are weak (★ smoke/existence/trivial, or a new test failing the authoring gate below) stays a coverage gap at its existing severity; a low-value test never closes a gap
Raw lines 90 to 97
98### Low-value or implementation-coupled tests
99Scope: tests and test-only production seams added or changed in the diff. Findings are
100INFORMATIONAL, never CRITICAL, and never an auto-delete: recommend rewrite at the owning
101boundary, extend an existing test, or retire with a complete retirement card
102(`test, detects, non_test_callers, search_command, stronger_proof, history, unlocks,
103validation`). End each finding's `fix` with `Repo-wide sweep: run /test-audit.`
105A new or changed test passes the authoring gate only when all four answers exist (read
106its `Value: protects=...; fails_when=...; why_new=...; seam=...` header comment when the
107diff has one; an optional `no_claim=...` names what green does not prove and which
108fixtures or mocks stand in for what, and never waives a required live check):
1091. What observable behavior, invariant or independent contract does it protect?
1102. What credible regression makes it fail?
1113. Why does existing coverage not already catch that? Prefer adding a row to an existing table-driven test or shared fixture over a near-duplicate.
1124. Does it need a production seam (export, flag, wrapper, injection hook) that no production caller needs? If yes, test at the real boundary instead.
114Patterns:
115- assertion-free coverage probes
116- self-comparisons and identity copies
117- copied fixtures, inventories or export lists
118- exact source, import or string greps that are not a declared contract
119- private predicate or call-shape tests duplicated at a real boundary
120- duplicate invocations of the same contract
121- per-caller replays of a shared helper's tests
122- tests whose only purpose is keeping a test-only export, global or wrapper alive
123- production code whose only callers are tests
125Retention bar (never flag): a test that independently enforces a public API, protocol,
126config, migration, storage, security, platform, default, prompt-byte, generated-output
127(SKILL.md golden), package, release or architecture contract; call order when order is
128observable; source inspection when it is the cheapest independent guard; anything
129reachable from the package entrypoint (`package.json` exports/main, index re-exports).
130Static or slow is not a reason to delete.
132Evidence for each finding goes in `evidence` with the fields `detects` (what failure the
133test can actually detect), `non_test_callers`, `search_command` and `stronger_proof`.
134For the last two patterns, run the caller check for each symbol the diff adds or exports,
135only when the symbol matches `^[A-Za-z_][A-Za-z0-9_]*$` (otherwise record "caller check
136unavailable: unsupported symbol"), with the symbol quoted, never interpolated unquoted:
138```bash
139git grep -n -F -w -e '<symbol>' -- . ':!test/' ':!tests/' ':!spec/' ':!**/__tests__/**' ':!**/*.test.*' ':!**/*.spec.*' ':!**/*_test.*' ':!**/test_*.py'
140```
142Record the command, exclusions and hit count, and mark the evidence "grep-only" (it cannot
143see re-exports, dynamic dispatch or generated code). If the search fails, record: caller
144check unavailable: <command>. The finding stays INFORMATIONAL and nothing is proposed for
145deletion; run the search by hand to complete the evidence. (see
146~/.claude/skills/gstack/docs/test-value-bar.md#caller-check-unavailable)
148Skip a test carrying `gstack:test-value keep reason="<why>"` (any comment syntax). If any
149were skipped, report their count and reasons as one INFORMATIONAL line. Emit no finding
150for a test you reviewed and kept.
152Rejection vocabulary, when a finding names why a new test fails the gate:
153`duplicate_protects`, `needs_seam`, `incomplete_card`, `no_credible_regression`,
154`covered_elsewhere`, `implementation_coupled`.

Low-value or implementation-coupled tests

Scope: tests and test-only production seams added or changed in the diff. Findings are INFORMATIONAL, never CRITICAL, and never an auto-delete: recommend rewrite at the owning boundary, extend an existing test, or retire with a complete retirement card (test, detects, non_test_callers, search_command, stronger_proof, history, unlocks, validation). End each finding's fix with Repo-wide sweep: run /test-audit.

A new or changed test passes the authoring gate only when all four answers exist (read its Value: protects=...; fails_when=...; why_new=...; seam=... header comment when the diff has one; an optional no_claim=... names what green does not prove and which fixtures or mocks stand in for what, and never waives a required live check):

  1. What observable behavior, invariant or independent contract does it protect?
  2. What credible regression makes it fail?
  3. Why does existing coverage not already catch that? Prefer adding a row to an existing table-driven test or shared fixture over a near-duplicate.
  4. Does it need a production seam (export, flag, wrapper, injection hook) that no production caller needs? If yes, test at the real boundary instead.

Patterns:

  • assertion-free coverage probes
  • self-comparisons and identity copies
  • copied fixtures, inventories or export lists
  • exact source, import or string greps that are not a declared contract
  • private predicate or call-shape tests duplicated at a real boundary
  • duplicate invocations of the same contract
  • per-caller replays of a shared helper's tests
  • tests whose only purpose is keeping a test-only export, global or wrapper alive
  • production code whose only callers are tests

Retention bar (never flag): a test that independently enforces a public API, protocol, config, migration, storage, security, platform, default, prompt-byte, generated-output (SKILL.md golden), package, release or architecture contract; call order when order is observable; source inspection when it is the cheapest independent guard; anything reachable from the package entrypoint (package.json exports/main, index re-exports). Static or slow is not a reason to delete.

Evidence for each finding goes in evidence with the fields detects (what failure the test can actually detect), non_test_callers, search_command and stronger_proof. For the last two patterns, run the caller check for each symbol the diff adds or exports, only when the symbol matches ^[A-Za-z_][A-Za-z0-9_]*$ (otherwise record "caller check unavailable: unsupported symbol"), with the symbol quoted, never interpolated unquoted:

git grep -n -F -w -e '<symbol>' -- . ':!test/' ':!tests/' ':!spec/' ':!**/__tests__/**' ':!**/*.test.*' ':!**/*.spec.*' ':!**/*_test.*' ':!**/test_*.py'

Record the command, exclusions and hit count, and mark the evidence "grep-only" (it cannot see re-exports, dynamic dispatch or generated code). If the search fails, record: caller check unavailable: <command>. The finding stays INFORMATIONAL and nothing is proposed for deletion; run the search by hand to complete the evidence. (see ~/.claude/skills/gstack/docs/test-value-bar.md#caller-check-unavailable)

Skip a test carrying gstack:test-value keep reason="<why>" (any comment syntax). If any were skipped, report their count and reasons as one INFORMATIONAL line. Emit no finding for a test you reviewed and kept.

Rejection vocabulary, when a finding names why a new test fails the gate: duplicate_protects, needs_seam, incomplete_card, no_credible_regression, covered_elsewhere, implementation_coupled.

Raw lines 98 to 155
Starts an agent dispatch :143
156### Regression test without red proof
157- The diff adds a test named or commented as a regression, and neither the commit history
158 nor the PR body shows it failing before the fix. A regression test that never
159 demonstrably failed proves the mock, not the fix. INFORMATIONAL; ask for the
160 fails-at-HEAD / passes-after-fix record.

Regression test without red proof

  • The diff adds a test named or commented as a regression, and neither the commit history nor the PR body shows it failing before the fix. A regression test that never demonstrably failed proves the mock, not the fix. INFORMATIONAL; ask for the fails-at-HEAD / passes-after-fix record.
Raw lines 156 to 160