Finds and removes redundant tests — tests that cover the same code, kill the same mutants, or assert the same behavior — to shrink suite runtime without losing coverage. Use when the test suite is slow, when tests have accumulated over years of copy-paste, or when CI costs are too high.
Install with Tessl CLI
npx tessl i github:santosomar/general-secure-coding-agent-skills --skill test-deduplicator97
Quality
96%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Ten tests cover the same branch. Nine of them can go. But which nine? The one you keep is the one that covers something the others don't.
| Sameness level | Evidence | Deletion confidence |
|---|---|---|
| Textual clone | Near-identical code, one value different | Low — might test different boundaries |
| Same coverage | Per-test coverage sets are equal | Medium |
| Same coverage, subsumes | Test A's coverage ⊇ Test B's | High — B is redundant |
| Same mutants killed | Mutation testing: A and B kill identical mutant sets | High |
| Same assertions, same inputs | Literally the same test, different name | Very high — copy-paste accident |
Coverage subsumption is the key signal. If test A covers everything test B covers and more, B adds nothing.
# Python — run each test in isolation, collect coverage
pytest --cov=src --cov-context=test --cov-report=
coverage json -o coverage.json
# coverage.json now has per-context (per-test) line data# Java — JaCoCo per-test is harder; use test-impact tools or:
# Run suite once, instrument to log (test_name, covered_line) pairsOutput: {test_name: set(covered_lines)}
# A subsumes B if coverage[A] >= coverage[B] (superset)
subsumed = []
tests = sorted(coverage, key=lambda t: len(coverage[t]), reverse=True)
for i, a in enumerate(tests):
for b in tests[i+1:]:
if coverage[a] >= coverage[b]: # a's coverage is a superset
subsumed.append((b, a)) # b is subsumed by aThis is O(n²) set comparisons. Fine for a few thousand tests. For tens of thousands, sort by size and prune.
Subsumption alone misses partial overlap. For the minimum set of tests with the same total coverage, use greedy set cover:
covered = set()
keep = []
remaining = sorted(coverage, key=lambda t: len(coverage[t]), reverse=True)
total = set().union(*coverage.values())
for t in remaining:
new_lines = coverage[t] - covered
if new_lines:
keep.append(t)
covered |= coverage[t]
if covered == total:
break
delete_candidates = set(coverage) - set(keep)Greedy isn't optimal (set cover is NP-hard) but it's within ln(n) of optimal and runs in seconds.
Coverage equivalence ≠ behavioral equivalence. Two tests can cover the same lines but assert different things:
def test_parse_int():
assert parse("42") == 42 # covers lines 10-15
def test_parse_int_leading_zero():
assert parse("042") == 42 # covers lines 10-15 — same coverage!Same coverage. Different inputs. The second might catch a bug the first doesn't (octal interpretation, anyone?).
Before deleting, check mutation-kill equivalence: run mutation testing (→ mutation-test-suite-optimizer) on the delete candidates. If a candidate kills a mutant no keeper kills, it's not redundant.
Often the "duplicates" are value variations:
def test_discount_gold(): assert discount(100, "gold") == 80
def test_discount_silver(): assert discount(100, "silver") == 90
def test_discount_bronze(): assert discount(100, "bronze") == 95
def test_discount_none(): assert discount(100, "none") == 100Four tests, near-identical structure. Don't delete three — parametrize:
@pytest.mark.parametrize("tier,expected", [
("gold", 80), ("silver", 90), ("bronze", 95), ("none", 100),
])
def test_discount(tier, expected):
assert discount(100, tier) == expectedSame coverage. Same assertions. One-quarter the code. Still four test cases.
| Pattern | Why not redundant |
|---|---|
| Same function, one mocks DB, one hits real DB | Unit vs integration — different failure modes |
| Same logic, different input sizes (1 vs 10000) | The big one catches O(n²) performance bugs |
| Same coverage, one asserts exception message | Error-path granularity |
| Flaky test + reliable test covering same thing | Delete the flaky one, keep the reliable one — but this is a flake fix, not dedup |
test_empty_list_returns_empty is worth keeping for the name, even if test_various_inputs also covers it.## Suite before
Tests: <N> Runtime: <s> Coverage: <%> line, <%> branch
## Subsumption
| Subsumed test | Subsumed by | Coverage delta |
| ------------- | ----------- | -------------- |
## Greedy minimum cover
Keep: <N> tests → same total coverage
Delete candidates: <M> tests
## Mutation verification
| Candidate | Mutants only this test kills | Keep? |
| --------- | ---------------------------- | ----- |
## Parametrization opportunities
| Tests | Merged into | Cases |
| ----- | ----------- | ----- |
## Final
| Action | Count |
| ------ | ----- |
| Delete (fully subsumed, no unique mutants) | <N> |
| Parametrize | <N> groups → <M> tests |
| Keep (looked redundant, isn't) | <N> |
## After
Tests: <N> Runtime: <s> Coverage: <%> (unchanged) Mutation score: <%> (unchanged)47d56bb
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.