Skip to content
Back to skills

Dense Testing

ASecurity

Build the test grid that makes AI-written code safe — traceability from spec UC ids to tests, density floors per changed symbol, and a hard ban on skipping or weakening tests to get green. Use when deciding how many tests a change needs, wiring acceptance criteria to test names, generating tests for agent-written code, or when a suite is green but trust in it is low.

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 21, 2026
testingpythonrustgobashtestingapidatabase

Works with

  • api

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned October 21, 2026

npx -y skills add emgiezet/aisdlc --skill dense-testing --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Dense Testing?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Dense Testing
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/emgiezet-dense-testing/badge)](https://www.skillsdirectory.com/skills/emgiezet-dense-testing)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: dense-testing
description: >
  Build the test grid that makes AI-written code safe — traceability from spec UC ids to
  tests, density floors per changed symbol, and a hard ban on skipping or weakening tests to
  get green. Use when deciding how many tests a change needs, wiring acceptance criteria to
  test names, generating tests for agent-written code, or when a suite is green but trust in
  it is low.
---

# Dense Testing

When agents write the code, tests stop being a quality ritual and become the only mechanism
that catches a hallucination. The grid has to be dense enough that a plausible-but-wrong
implementation cannot slip between the holes.

The counter-intuitive part: **at scale, test count matters more than the elegance of any
single test.** A thousand blunt tests that each pin one observable behaviour catch more
agent defects than fifty beautifully-factored ones, because agent errors are scattered and
specific rather than systematic. Optimise for coverage of behaviours, not for DRY test code.

Stack conventions and commands live elsewhere: this project's `.claude/rules/` files, its
`.claude/sdlc.md` verification table, and any stack pattern skills it happens to have. This skill
is the *how much* and the *what must never happen*.

## Traceability: UC id in the test name

Every acceptance criterion in `specs/<TICKET>/spec.md` carries a stable `UC-<n>` id. Every
test that proves one carries that id in its name or annotation:

```go
func TestGetBalance_UC1_ReturnsIntegerMinorUnits(t *testing.T) { … }
```
```ts
it('UC-3: lists entries newest first', async () => { … })
```
```python
def test_uc2_rejects_a_total_that_does_not_reconcile(): ...
```

This makes coverage of the *spec* computable with grep, independent of line-coverage tools:

```bash
grep -rho 'UC-[0-9]\+' <test-dir> | sort -u    # UCs with at least one test
```

`/sdlc:qa` and the `auto-qa` agent build the UC×test matrix from exactly this. A UC with no
matching test is a gap, whatever the line coverage says.

## Density floors

Minimums, not targets. Applied to the diff, not the whole repo:

| Changed thing | Minimum tests |
|---------------|---------------|
| Acceptance criterion (`UC-<n>`) | 1 integration test carrying its id |
| Public function / method / exported symbol | 1 unit test for the happy path |
| Error return or thrown exception | 1 case per distinct error |
| Validation rule | 1 rejection case per rule, asserting no side effect |
| HTTP endpoint | happy path + validation rejection + authorization denial + not-found |
| Conditional branch | 1 case per side |
| UI component with logic | 1 case per render state: populated, empty, loading, error, denied |
| Multi-screen flow in the spec | 1 Playwright scenario |
| Migration | up→down→up on a scratch DB, plus 1 case per new constraint |
| Bug fix | 1 test that fails before the fix and passes after |

Table-driven or parameterised tests satisfy several rows at once and are the cheapest way to hit
the branch floor — one case per branch, which is also how a weak model reliably generates them.

## Assert on observable behaviour

Assert what a caller can see: status code, response body, database row, rendered text,
emitted event, log line. Not: that a mock was called, that an internal method ran, that a
private field holds a value. A test coupled to the implementation passes for a
reimplementation that is wrong, which is the exact failure this grid exists to catch.

Mock only what you cannot run: third-party APIs, clocks, randomness, payment providers. Never mock
your own database or your own service layer in an integration test.

## Non-negotiable

These are enforced by the `Stop` hook, which blocks a session that violates them:

1. **Never delete a test** to make a suite green.
2. **Never skip a test** — `t.Skip`, `.skip(`, `.only(`, `markTestSkipped`,
   `@pytest.mark.skip`, `@Ignore`, commenting a test out.
3. **Never weaken an assertion** — loosening an exact match to a substring, removing a field
   check, widening an expected range, replacing an assertion with a log.
4. **Never assert current buggy behaviour** just to get green.

When a test contradicts the spec, **stop and report the contradiction.** One of the two is
wrong and deciding which is a human call, not a cleanup task. In an unattended run, write the
contradiction to `specs/<TICKET>/BLOCKED.md` and exit non-zero.

A test that is genuinely obsolete — its feature was removed, or a refactor folded it into
another file — is **retired**, not deleted quietly: add `<path-or-glob> <reason>` to
`.claude/retired-tests` (committed; `#` comments) and both the `Stop` hook and the runner's
`verify` gate let that deletion through.
An entry without a reason does not count. Unattended runs (`AISDLC_HEADLESS=1`) honour only
entries already on the default branch, so a human commits them — typically alongside the spec.
Skipped, focused or weakened tests are never retirable.

A flaky test is a defect in the test or the fixture. Fix the race, the shared state, or the
clock dependency. Never add a retry wrapper to hide it.

## End-to-end tests for spec flows

For multi-screen rows (`test: e2e`), one scenario per flow with whichever runner the project
records in `.claude/sdlc.md`, named with the use-case id, driving the real UI against a seeded
database. Assert on user-visible text and roles. Capture a screenshot at the point the result
becomes visible and attach it to the QA report — the cheapest evidence a human can check in
seconds. Keep the e2e count low and the integration count high: e2e is where suite runtime goes
to die.

## Reporting

After a run, report in the spec's terms, not the tool's:

```
UC coverage: 7/8 (UC-6 has no test)
Tests: 412 → 448 (+36)
Suites: <each one you ran> ✓
Skipped: 0        # any non-zero value is a finding, not a footnote
```

Coverage percentage is a secondary signal. `UC` coverage and skip count are the primary ones.

Files in this skill

  • SKILL.md5.8 KB
  • agents/eval-set.json999 B
  • references/ci-matrix.md3.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.