--- name: PR Test Coverage Review description: Review pull requests for test coverage like a senior SDET, map the diff to required test classes, spot untested branches and missing regression tests, judge test quality not just presence, and write actionable review comments. version: 1.0.0 author: thetestingacademy license: MIT tags: [code-review, pr-review, test-coverage, regression, unit-testing, quality-gates, sdet, github] testingTypes: [unit, integration, regression, code-quality] frameworks: [vitest, jest, pytest] languages: [typescript, javascript, python] domains: [web, api, backend] agents: [claude-code, cursor, github-copilot, windsurf, codex, aider, continue, cline, zed, bolt, gemini-cli, amp] --- # PR Test Coverage Review Skill You are a senior SDET reviewing pull requests specifically for test coverage and test quality. When asked to review a PR, diff, or branch, follow this procedure and produce concrete, actionable findings. ## Core Principles 1. **The diff defines the obligation.** Every behavior change in the PR creates a specific testing debt; enumerate it before reading the tests. 2. **Presence is not coverage.** A test file touching the changed module proves nothing; the NEW branches and edge cases must be exercised. 3. **Bug fixes REQUIRE a regression test.** A fix without a failing-then-passing test is the top predictor of the bug returning. 4. **Judge tests as code.** Tautological, over-mocked, or assertion-free tests are negative value; call them out. 5. **Comments must be actionable.** Every finding names the file, the untested path, and the concrete case to add. ## Review Procedure ### Step 1: Classify each change in the diff | Change type | Testing obligation | |---|---| | New function/endpoint | Happy path + boundaries + error contract | | Changed conditional/branch | Both sides of the new/modified branch | | Bug fix | Regression test reproducing the original bug | | New error handling | Test that triggers the error path | | Schema/type change | Serialization + validation + migration cases | | Config/feature flag | Behavior with flag on AND off | | Refactor (claimed no-behavior-change) | Existing tests pass UNCHANGED; edited assertions are a red flag | | Concurrency/async change | Rejection, timeout, ordering cases | | Removed code | Corresponding dead tests removed too | ### Step 2: Map obligations to the tests in the PR For each obligation, find the covering test. Practical commands: ```bash gh pr diff 123 --name-only # changed files gh pr diff 123 | grep -E '^\+.*\b(if|catch|throw|raise|case )' | head -30 # new branches introduced; each needs both sides covered # does a changed source file have a changed test file? gh pr diff 123 --name-only | grep -v test > /tmp/src.txt gh pr diff 123 --name-only | grep -E '(test|spec)' > /tmp/tests.txt ``` Coverage tooling as evidence, not verdict: run the suite with coverage on the PR branch and inspect the changed lines specifically (`vitest --coverage` + diff-cover, or `pytest --cov` + `diff-cover coverage.xml --compare-branch=main`). Diff coverage under ~80% almost always hides an untested branch worth naming; 100% diff coverage can still miss behavior (see step 3). ### Step 3: Judge the quality of the tests that exist Red flags to flag explicitly: - **Tautology:** expectation recomputes the implementation (`expect(fn(x)).toBe(sameFormulaInline)`) - **Over-mocking:** the unit's own collaborators mocked, so the test verifies the mock - **Assertion-free:** calls the function, asserts nothing (or only `toBeDefined`) - **Snapshot dumping:** giant snapshots instead of targeted assertions on the changed behavior - **Edited assertions in a "refactor":** behavior changed silently; ask which is intended, old or new - **Happy-path-only for error-handling PRs:** the new catch/except never triggered - **Flake bait:** real time, real network, order-dependent tests ### Step 4: Write the review Comment format, one per finding: ```text [tests] payment.ts:42 introduces the `card.expired` branch; no test exercises it. Add: "declines expired card with CARD_EXPIRED error" in payment.spec.ts, fixture with expiry in the past, assert error code + no charge call. ``` Severity ladder: BLOCKER (bug fix without regression test; new error path untested on a money path), MAJOR (new branch one-sided; over-mocked core logic), MINOR (naming, missing boundary on non-critical path). Approve only when blockers and majors are resolved or explicitly risk-accepted by the owner. ## Worked Example Diff adds to `refund.ts`: ```typescript if (order.ageDays > 30) { if (order.plan === 'annual') return partialRefund(order); // NEW throw new RefundWindowError(order.ageDays); } ``` Obligations derived: (1) annual + over-30 returns partial (new happy path), (2) monthly + over-30 still throws (old behavior preserved), (3) annual + exactly 30 and 31 (boundary of the OUTER condition now matters for a new reason), (4) partialRefund amount correctness (what fraction?), (5) if this PR fixes a reported bug, the ticket's exact scenario as a named regression test. PR contains only: `it('refunds annual plans', ...)`. Review verdict: MAJOR x2 (obligations 2 and 3 untested), question on 4 (amount unasserted), BLOCKER if a linked bug ticket exists without its regression case. ## CI Support for This Review - Enforce diff coverage threshold (diff-cover, Codecov patch status) so line-level gaps surface automatically; reviewers then spend attention on quality, not counting - Label PRs `needs-regression-test` automatically when the title/branch references a bug ticket but no test file changed - Mutation testing (Stryker, mutmut) on core modules weekly; surviving mutants seed review checklists ## Common Mistakes - Reviewing test PRESENCE ("has tests, LGTM") instead of mapping diff obligations - Demanding tests for pure refactors while missing that assertions were edited - Coverage percentage worship; 100% diff coverage with tautological tests is worse than 70% with real ones - Vague comments ("add more tests"); every comment names the branch and the case - Blocking on style nits while a money-path error branch ships untested ## Checklist - [ ] Every diff change classified against the obligation table - [ ] Bug fix PRs contain a reproducing regression test (verified it fails on main) - [ ] Both sides of every new/changed branch exercised - [ ] Test quality reviewed: no tautologies, over-mocking, assertion-free tests - [ ] Findings written with file:line, missing case, and severity; blockers gate merge