by thetestingacademy
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.
npx @qaskills/cli add pr-test-coverage-reviewAuto-detects your AI agent and installs the skill. Works with Claude Code, Cursor, Copilot, and more.
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.
| 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 |
For each obligation, find the covering test. Practical commands:
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).
Red flags to flag explicitly:
expect(fn(x)).toBe(sameFormulaInline))toBeDefined)Comment format, one per finding:
[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.
Diff adds to refund.ts:
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.
needs-regression-test automatically when the title/branch references a bug ticket but no test file changed- name: Install QA Skills
run: npx @qaskills/cli add pr-test-coverage-review12 of 29 agents supported
Build AI agents that write, run, and fix tests. Playwright, LLM evals, and CI in one live cohort.
Use code AITESTER at checkout