A test suite can execute the same code hundreds of times and still miss a bug. Coverage tells us whether code ran. It does not tell us whether a test would notice the wrong result.
That distinction also matters when removing tests. Two tests can have identical coverage and protect different behavior. A test with much broader coverage can have weaker assertions than a small unit test.
So how do we find redundant tests without reading the entire suite?
I built dead-weight-tests around two separate tasks. Measure which tests execute overlapping code. Then compare what those tests actually check. The Rust CLI, dwd, does the measurement. An agent skill guides the assertion review.
The useful output is a set of specific test pairs worth inspecting, with the evidence needed to decide whether to cut, merge, keep, or fix them.
Suite coverage loses the relationship we need
A conventional coverage report combines execution across a test suite. If any test runs a region, that region appears covered.
This is useful for finding code that no test executes. But it discards the relationship between a test and the code it runs. A region covered by one test and a region covered by fifty tests both appear covered.
To investigate redundancy, we need that relationship back. For each test t, define C(t) as the set of production-code regions it executes.
A region is an LLVM source span, identified by its file and start and end positions. It can cover part of a line. We use set membership, so executing a region a hundred times still adds one member to the set.
This gives us a concrete question: does this test execute any production code that the comparison test does not?
It does not yet answer whether either test checks that code correctly.
Collect coverage separately for each test
Rust already has a useful process boundary for this. cargo-nextest runs each test in its own process.
dwd collect uses cargo-llvm-cov to enable LLVM coverage instrumentation. It installs dwd as nextest’s target runner, the program that launches each test process. The runner gives each test its own profile directory through LLVM_PROFILE_FILE.
When the test exits, its profile records which instrumented regions ran. The collector merges and exports profiles separately for each passing test. Child processes can contribute to the same test’s coverage because they inherit the profile environment. A child that clears that environment will not contribute through this mechanism.
Failed tests and missing profiles need separate treatment. The collector records skipped tests and their reasons instead of treating an incomplete run as a valid empty coverage set.
Then the analyzer removes test-code regions. This matters more than it first appears. Every test executes its own body and assertions. If we include those regions, two tests with identical production coverage still look different because each runs its own test code.
The exclusion uses source scanning and naming rules. Unusual test layouts can need inspection. The result is an observed set of production regions for each collected test, under the build configuration and inputs used in that run.
Compare the sets
Once we have per-test sets, we can find three relationships without asking a model to read code.
| Relationship | Condition | What it establishes |
|---|---|---|
| Duplicate | A = B | Both tests execute the same regions. |
| Subsumed | A is a strict subset of B | Every region executed by A is also executed by B. |
| Near duplicate | High overlap, with neither set containing the other | Each test also executes regions the other does not. |
For near duplicates, dwd uses the Jaccard coefficient:
J(A, B) = number of shared regions / number of regions in either setThe default threshold is 0.95. This score measures execution overlap. It is not a probability that a test is redundant.
Containment needs its own check. A small test can sit entirely inside a much larger test and still have a low Jaccard score. The larger test’s additional regions increase the denominator. Filtering only for high similarity would miss that relationship.
The implementation also compresses the comparison data. Regions covered by exactly the same tests form a coverage class. Each class retains its region count as a weight, so similarity still measures regions rather than the number of classes. Subset and near-pair searches use indexes to narrow candidates before exact comparison.
This produces pairwise review candidates. It does not solve for the smallest sufficient test suite.
Identical coverage can protect different behavior
Consider these assertions from the tool’s fixture:
assert_eq!(
parse_number("42"), Ok(42)
);
assert_eq!(
parse_number(" 7 "), Ok(7)
);Both tests execute the same 11 production-code regions. The parser trims whitespace, then parses the number.
Remove trim() and the first assertion still passes. The second fails.
Original parser: both tests cover the same 11 regions.
trim()Change the implementation, then rerun both tests.parse_number(“42”)PASS. Plain-number parsing still works.parse_number(” 7 “)FAIL. Whitespace handling is gone.The second test protects whitespace handling. That difference exists in the input and expected result, even though the original executions cover identical regions. The coverage sets do not preserve input values or describe which implementation changes would make an assertion fail.
So a duplicate label is a reason to compare the tests. It cannot justify a deletion by itself.
The same principle applies to boundaries and error cases. Two tests may execute the same branch with different values that matter to the contract. Review needs to compare the input classes, setup, expected results, and failure conditions.
Broader coverage can have weaker assertions
Here’s a pair from Pippin, my Rust Discord bot. One test builds a UI section and checks that it contains one text component and a thumbnail accessory. Another builds the complete message screen.

The smaller test has 18 covered regions. The larger test covers all of them and 47 more, but lacks the thumbnail assertion.
The larger test checks the enclosing container and its child count. It executes the thumbnail construction code without checking that the thumbnail exists.
Its coverage contains the smaller test’s coverage. Its assertions do not replace the smaller test’s assertions.
The report recommends moving the text-component and thumbnail assertions into the screen test before removing the smaller test. That is a merge, not a straight cut. The surviving test needs those checks first.
Even a possible merge needs judgment. A focused unit test can provide better failure diagnosis. It may also run without the services an integration test needs. Shared execution does not make those differences irrelevant.
Make assertion review a bounded task
A request to “find useless tests” leaves an agent to choose what to read and what counts as useless. Per-test coverage supplies specific pairs, source locations, and measured overlap.
The skill then directs the agent to read both tests and the relevant production code. It records a verdict with a reason:
| Verdict | Reason |
|---|---|
| Cut | The other test checks the same behavior with equal or stronger assertions. |
| Merge | Distinct assertions can move into a suitable surviving test. |
| Keep | The test protects distinct behavior or has a useful testing role of its own. |
| Fix | The test promises a check that its body does not make. |
The CLI does not call a model to make these decisions. The agent performs the review, and dwd verdict validates and records the result. Validating a verdict’s structure does not prove its reasoning is correct.
This separation is useful. Set operations handle the measurable execution relationships. The agent spends its context on the inputs and assertions of identified pairs. A reviewer can inspect both the measurement and the proposed change.

The graph groups tests by source file and links coverage relationships. Verdict colors show the recorded review decisions.
The report also provides test bodies side by side and production-source coverage differences. The graph helps locate related tests. The pair view gives the evidence needed to assess a recommendation.
Check the proposed changes together
A pairwise recommendation depends on another test remaining available. If several recommendations remove each other’s comparison tests, reviewing each pair alone can hide the combined loss.
The current verdict report therefore calculates which observed regions would become uncovered after the proposed cuts and merges. This checks the combined removal set against the collected coverage.
That is still an execution check. Zero lost regions does not prove that all assertions or input cases survive. A merge must preserve its intended assertions, and a fix must actually test the promised behavior.
The workflow presents the proposed changes for approval before editing tests. After approved edits, run the affected tests. Recollect coverage when checking the resulting suite. For an important assertion, deliberately breaking the behavior can also establish whether the test detects that failure.
Where this helps
This is useful when a suite has grown beyond what one person can keep in their head. Agents make it easy to add tests. Teams accumulate overlapping tests too. Neither the author nor the test count tells us which checks are worth keeping.
Per-test coverage reduces the search to concrete execution relationships. Assertion review then distinguishes repeated checks from meaningful input and behavior differences.
Removing redundant tests can reduce repeated execution and maintenance. It does not guarantee a proportional CI speedup. Compilation, setup, parallel scheduling, and the slowest job can dominate elapsed time. Measure those separately if runtime is the goal.
The tool currently works on Rust workspaces using nextest, on Linux or macOS. Doctests and non-host targets are outside this collection path. A scoped run only compares tests inside that scope. Nondeterministic execution and build configuration can also change the observed sets.
Install the skill with:
npx skills add roberts-pumpurs/dead-weight-testsThen ask your coding agent to find dead weight tests. The README covers setup and direct CLI use.
For each proposed removal, ask which behavior the surviving tests still check. Matching coverage makes that question easier to investigate. It does not make the question disappear.
See the tool in use
This walkthrough shows coverage collection, the report, and assertion review on a Rust project.