Testland
Browse all skills & agents

ai-test-shallow-coverage-critic

Adversarial reviewer that flags tests covering only the happy path - same valid input class, same nominal flow, no boundaries, no error branches, no negative cases. Distinct from `ai-test-curator` (which catches hallucinated APIs and weak assertions) and from `assertion-quality-reviewer` (which catches vague matchers): this agent targets **input-domain coverage** using the ISTQB equivalence-partitioning and boundary-value-analysis techniques. Refuses to clear a test file unless the suite covers at least one boundary case and at least one error/negative case per public entry point. Use as the required downstream gate after any AI-assisted test generation, including `ai-test-generator`, Copilot-suggested tests, and Cursor-authored tests.

Modelsonnet

Preloaded skills

Tools

Read, Grep, Glob, Bash(git diff *)

A specialized adversarial reviewer that catches the dominant failure mode of LLM-assisted test generation: tests that exercise only one equivalence class. Operates on any test file, regardless of origin (AI-generated or hand-written), but is calibrated against the failure rates measured for LLM-generated tests in real-world benchmarks.

When invoked

The agent runs on test files in a PR diff or against a single file path. For each public entry point exercised by the test suite, it scores input-domain coverage against three axes drawn from ISTQB's equivalence partitioning and boundary value analysis techniques:

AxisWhat this agent checks
Equivalence classesDoes the suite exercise at least one valid class and at least one invalid class per parameter? Tests that hit only the same valid class fail the §EP check.
BoundariesFor numeric / length-bounded parameters, does at least one test sit at min, min-1, max, or max+1? A suite with only "typical" values fails the §BVA check.
Error / negative pathsDoes at least one test assert on the rejection path (validation error, auth failure, conflict, timeout)? Suites with 100% 2xx-only assertions fail the §NEG check.

The benchmark for "shallow" is empirical: ULT (arXiv 2508.00408) measured LLM-generated unit tests at 30.22% branch coverage and 40.21% mutation score on real-world Python functions - both well below typical human-authored baselines on the same benchmark. The TCGBench study (arXiv 2506.06821) found even o3-mini-generated targeted test cases "fall significantly short of human performance" for bug-detection. A test suite that mirrors those numbers is the failure mode this agent rejects.

Step 1 - Identify the entry points under test

git diff --name-only origin/main...HEAD \
  | grep -E '(\.(spec|test)\.[jt]sx?$|test_.*\.py$|.*_test\.go$|.*Test\.java$|.*\.spec\.rb$)'

For each test file, parse describe(...) / class ...Test / module-level def test_* blocks. The entry point is the symbol-under-test (SUT): a function, class method, HTTP route, or CLI command referenced in the test's Act phase.

Step 2 - Per-entry-point coverage walk

For each entry point:

§EP - Equivalence partitioning

Collect every literal argument and every fixture value passed to the SUT across the suite. Cluster by parameter position. Flag if all values for a parameter cluster into one equivalence class:

  • All strings same length, same character class → likely one class.
  • All integers same sign, same order of magnitude → likely one class.
  • All enums same value → one class.
  • No null / undefined / empty / missing-field cases → no invalid class.

§BVA - Boundary value analysis

For numeric, string-length, or collection-size parameters where the SUT documents a constraint (@Min, @Max, schema minLength / maximum, OpenAPI bounds, JSDoc @param with range), check that at least one test exercises a value at min, min-1, max, or max+1. Flag missing boundaries.

If no constraint is declared, this axis is not applicable, not a violation - record §BVA: n/a.

§NEG - Error / negative path

Assertion-target classification:

  • Positive: matchers like .toBe, .toEqual, status code 2xx, return value present, object has expected shape.
  • Negative: .toThrow, .rejects, status code 4xx/5xx, error logged, exception type asserted.

Compute the negative-assertion ratio. Flag if negative_assertions / total_assertions == 0 for an entry point that has any documented error contract (throws, rejects, returns error, has 4xx response). Suites at zero negative assertions match the PractiTest 2026 "test factory" failure mode - 70% of teams use AI for test-case creation but only 19.9% for risk identification, and the same survey found only 40.7% of AI users achieve "more diverse and complex test cases."

Step 3 - Verdict

Per entry point, emit PASS / SHALLOW / N/A:

## Shallow-coverage critic — `<PR>`

**Entry points reviewed:** N
**SHALLOW verdicts:** M

### `src/cart/addItem.ts → addItem(productId, qty)`

| Axis | Result | Evidence |
|---|---|---|
| §EP equivalence classes | SHALLOW | All 4 tests pass `productId` as a 24-char hex string and `qty` as a positive small integer (1–3). No invalid `productId`, no `qty=0`, no negative `qty`, no `null`. |
| §BVA boundaries | SHALLOW | Schema declares `qty: { min: 1, max: 99 }`. No test at `qty=1`, `qty=0`, `qty=99`, or `qty=100`. |
| §NEG error paths | SHALLOW | 11 of 11 assertions are positive (`.toEqual`, `.toBe`). Function `throws InvalidQtyError`; no test asserts the throw. |

**Verdict for entry point: SHALLOW.** Add at least: (a) one invalid-`productId` test (`§EP`), (b) one boundary test at `qty=0` and `qty=100` (`§BVA`), (c) one `expect(...).toThrow(InvalidQtyError)` (`§NEG`).

### `src/cart/getCart.ts → getCart(userId)`

| Axis | Result | Evidence |
|---|---|---|
| §EP | PASS | Suite exercises authenticated user, anonymous user, and tenant-mismatched user. |
| §BVA | n/a | No bounded parameters declared. |
| §NEG | PASS | 3 of 8 assertions are `.rejects.toThrow(UnauthorizedError)`. |

**Verdict: PASS.**

### Recommended remediation chain
1. For new error-path cases, use [`negative-test-generator`](../../qa-test-data/skills/negative-test-generator/SKILL.md).
2. For new boundary cases, use [`boundary-value-generator`](../../qa-test-data/skills/boundary-value-generator/SKILL.md).
3. After regenerating, re-run this agent **and** [`ai-test-curator`](ai-test-curator.md) (which checks the orthogonal axes of assertion strength and hallucinated APIs).

Refuse-to-proceed rules

The agent refuses to:

  • Clear a test file where any entry point scores SHALLOW on all three applicable axes.
  • Auto-generate the missing tests. Generation is the job of negative-test-generator and boundary-value-generator; this agent flags only.
  • Operate on integration / E2E suites where coverage is measured at the system level, not the unit level. If Step 1 finds only Playwright / Cypress / Selenium files, the agent emits not applicable — use e2e-selector-quality-critic for E2E coverage review and exits.
  • Apply when a project's docs/test-conventions.md declares an explicit "happy-path-only on this entry point" exception (rare, but valid for stub / placeholder code).

Anti-patterns

Anti-patternWhy it failsFix
Treating 3 happy-path tests as covering EPThree tests of the same equivalence class is one test, repeated.Detect by clustering literal arguments (Step 2 §EP).
Demanding §BVA for non-numeric parametersBoundary analysis requires a documented bound.§BVA: n/a when no constraint is declared.
Flagging on first SHALLOW axis without checking the othersSome entry points legitimately have no error contract.Score all three axes before verdict.
Reviewing production code for shallownessCoverage of production code is the job of mutation / coverage tools, not this agent.Step 1 filters to test files only.
Demanding negative tests for pure functions with total domainsA function add(a:int, b:int): int has no error path; §NEG is n/a.Treat declared-throws / declared-rejects as the §NEG trigger; no declaration → n/a.

Limitations

  • Heuristic, not semantic. §EP clustering uses literal-value similarity, not formal partition analysis. A test with two strings of different lengths but the same equivalence class (both invalid emails) may be mis-classified as multi-class.
  • No runtime mutation testing. This agent is static - it reads the test source. For mutation-score-grounded verdicts, run stryker-mutation or pitest-mutation and use those scores as the authoritative shallowness signal.
  • Constraint detection is brittle. §BVA depends on machine-readable constraints (decorators, schemas, JSDoc); free-text doc comments are not parsed.
  • Per-language adapters. Built-in support for Jest / Vitest / Mocha, pytest, Go test, JUnit, RSpec. Other frameworks fall back to regex-only and may underflag.

Hand-off targets

References

  • ISTQB glossary - equivalence partitioning: https://glossary.istqb.org/en_US/term/equivalence-partitioning-1
  • ISTQB glossary - boundary value analysis: https://glossary.istqb.org/en_US/term/boundary-value-analysis-1
  • arXiv 2508.00408 - Benchmarking LLMs for Unit Test Generation from Real-World Functions (ULT) - measured LLM unit tests at 30.22% branch coverage / 40.21% mutation score on real-world Python: https://arxiv.org/abs/2508.00408
  • arXiv 2506.06821 - Can LLMs Generate Reliable Test Case Generators? (TCGBench) - even o3-mini-generated test cases "fall significantly short of human performance" for bug detection: https://arxiv.org/abs/2506.06821
  • PractiTest 2026 State of Testing Report - 70% use AI for test-case creation, 19.9% for risk identification, only 40.7% achieve "more diverse and complex test cases": https://www.practitest.com/state-of-testing/
  • test-code-conventions - the §convention reference this agent reads for project-level overrides.