The testing stance says whether tests are required. This is how you find out whether the ones
you wrote are worth anything.
A suite can reach full line coverage with no assertions at all. Every line executes, nothing is
checked, and the gate is green. Coverage measures what ran; it does not measure what was
verified. Everything below exists to close that gap.
What to measure, in order of what it tells you
- Branch coverage, not line coverage. Line coverage counts a two-way branch as covered when
one side ran. Branch coverage is the cheapest upgrade available and usually the one that
reveals the untested error path.
- Mutation score. Change an operator, flip a boundary, delete a statement, then rerun the
suite. A mutant that survives is a change to your code no test objects to. This is the only
instrument here that measures assertions rather than execution, so it is the one that catches
an assertion-free suite.
- Complexity against coverage. A function that is both branchy and thinly covered is where
defects concentrate. Either number alone is weak; the pair ranks the work. Robert Martin's CRAP
formula is one published way to combine them, and any complexity report joined to a coverage
report gets you the same ranking.
- Duplication. A refactor signal, never a gate. Duplicated logic means a fix lands in one
copy. Do not fail a build on it, and do not let a tool talk you into a bad abstraction.
Per-language instruments
Verify each one against the licensing stance with the licensing-review skill before adopting
it. These run in CI rather than shipping inside the product, and the permissive-commercial stance
still makes no exception for tooling.
| Language |
Branch coverage |
Mutation |
Duplication |
| Python |
coverage.py with branch = true, usually via pytest-cov |
mutmut, or cosmic-ray for a larger tree |
pylint --enable=duplicate-code, or jscpd |
| TypeScript |
vitest --coverage or jest --coverage, branches threshold set |
Stryker Mutator |
jscpd |
| Rust |
cargo-llvm-cov |
cargo-mutants |
no standard tool worth adopting |
| Go |
go test -covermode=atomic -coverprofile |
go-mutesting |
dupl |
Read the tool's own documentation for flags before the first run. A stale flag in a skill is
worse than no flag, and these move.
How to run them
- Differentially, against what changed. Mutating a whole tree on every change buys a number
nobody reads and a loop nobody waits for. Mutate the diff. Reserve a full run for a release or
a scheduled job.
- One at a time. Coverage, mutation and duplication runs all spawn test processes. Run them
concurrently and they contend for the same CPU, the same ports and the same fixtures, and the
numbers get noisy in a way that looks like flakiness.
- Bounded workers. Pass an explicit worker limit rather than letting a tool take every core,
or an unrelated command in the same session will time out.
- Report progress on long runs. A mutation run over a large module is indistinguishable from a
hang without periodic output, and a killed run teaches nothing.
What to do with the numbers
- A surviving mutant is a missing assertion, so write the assertion. It is not a reason to
delete the mutant or add it to an ignore list.
- Separate the testable from the environment-bound. Code that opens a window, talks to a
device, or needs a network is not a fair subject for these instruments. Push logic out of it
until the untestable boundary is thin, then measure only the part that can be measured, and say
which part that is.
- Do not set a coverage threshold as the goal. A threshold is a floor that stops regression.
Chasing a number produces tests that execute code and assert nothing, which is the exact failure
mutation testing exists to find.
- Record the baseline in the repo's agent instructions the first time you run an instrument,
the way the verification gates record their clean-tree output, so a later movement is
attributable.
1---2name: code-quality-instruments3description: Measure whether a test suite is actually any good, rather than only that it exists and passes. Use when adding or reviewing tests on a change that matters, when a suite passes but a bug still shipped, when coverage is high and confidence is not, and before promising a module is well tested. Carries branch coverage, mutation score, the complexity-times-coverage risk signal, duplication, and the per-language instruments that produce each.4---56The `testing` stance says whether tests are required. This is how you find out whether the ones7you wrote are worth anything.89A suite can reach full line coverage with no assertions at all. Every line executes, nothing is10checked, and the gate is green. Coverage measures what ran; it does not measure what was11verified. Everything below exists to close that gap.1213## What to measure, in order of what it tells you14151. **Branch coverage, not line coverage.** Line coverage counts a two-way branch as covered when16 one side ran. Branch coverage is the cheapest upgrade available and usually the one that17 reveals the untested error path.182. **Mutation score.** Change an operator, flip a boundary, delete a statement, then rerun the19 suite. A mutant that survives is a change to your code no test objects to. This is the only20 instrument here that measures assertions rather than execution, so it is the one that catches21 an assertion-free suite.223. **Complexity against coverage.** A function that is both branchy and thinly covered is where23 defects concentrate. Either number alone is weak; the pair ranks the work. Robert Martin's CRAP24 formula is one published way to combine them, and any complexity report joined to a coverage25 report gets you the same ranking.264. **Duplication.** A refactor signal, never a gate. Duplicated logic means a fix lands in one27 copy. Do not fail a build on it, and do not let a tool talk you into a bad abstraction.2829## Per-language instruments3031Verify each one against the `licensing` stance with the `licensing-review` skill before adopting32it. These run in CI rather than shipping inside the product, and the permissive-commercial stance33still makes no exception for tooling.3435| Language | Branch coverage | Mutation | Duplication |36| --- | --- | --- | --- |37| Python | `coverage.py` with `branch = true`, usually via `pytest-cov` | `mutmut`, or `cosmic-ray` for a larger tree | `pylint --enable=duplicate-code`, or `jscpd` |38| TypeScript | `vitest --coverage` or `jest --coverage`, `branches` threshold set | Stryker Mutator | `jscpd` |39| Rust | `cargo-llvm-cov` | `cargo-mutants` | no standard tool worth adopting |40| Go | `go test -covermode=atomic -coverprofile` | `go-mutesting` | `dupl` |4142Read the tool's own documentation for flags before the first run. A stale flag in a skill is43worse than no flag, and these move.4445## How to run them4647- **Differentially, against what changed.** Mutating a whole tree on every change buys a number48 nobody reads and a loop nobody waits for. Mutate the diff. Reserve a full run for a release or49 a scheduled job.50- **One at a time.** Coverage, mutation and duplication runs all spawn test processes. Run them51 concurrently and they contend for the same CPU, the same ports and the same fixtures, and the52 numbers get noisy in a way that looks like flakiness.53- **Bounded workers.** Pass an explicit worker limit rather than letting a tool take every core,54 or an unrelated command in the same session will time out.55- **Report progress on long runs.** A mutation run over a large module is indistinguishable from a56 hang without periodic output, and a killed run teaches nothing.5758## What to do with the numbers5960- **A surviving mutant is a missing assertion**, so write the assertion. It is not a reason to61 delete the mutant or add it to an ignore list.62- **Separate the testable from the environment-bound.** Code that opens a window, talks to a63 device, or needs a network is not a fair subject for these instruments. Push logic out of it64 until the untestable boundary is thin, then measure only the part that can be measured, and say65 which part that is.66- **Do not set a coverage threshold as the goal.** A threshold is a floor that stops regression.67 Chasing a number produces tests that execute code and assert nothing, which is the exact failure68 mutation testing exists to find.69- **Record the baseline** in the repo's agent instructions the first time you run an instrument,70 the way the verification gates record their clean-tree output, so a later movement is71 attributable.