{5, 6});
```
But two tests with **identical values** ARE redundant:
```cpp
// test_frac_gaps.cpp โ values: gcd_recur(12, 8) = 4
CHECK_EQ(gcd_recur(12, 8), 4);
// test_coverage_gaps.cpp โ SAME values!
CHECK_EQ(gcd_recur(12, 8), 4);
```
--
**Verification:** Coverage must be **exactly equal** before and after removal. Not "close enough" โ **identical**.
---
### ๐ฏ Two Modes of Operation
.mermaid[
graph LR
A["Test Suite"] --> B{DEDUP Pass}
B -->|"Files with >90%
duplicate values"| C["Remove
redundant files"]
B -->|"Keep unique tests"| D["Surviving
test suite"]
C --> E["Coverage
comparison"]
E -->|"Unchanged โ"| F["Cleaner suite"]
E -->|"Dropped โ"| G["Revert!"]
D --> H{GAP-FILL Pass}
H -->|"gcov shows
untested code"| I["Write
targeted tests"]
H -->|"100% across
all headers"| J["Done โ"]
I --> K["Coverage
improvement"]
K --> J
]
---
class: nord-light, middle, center
## ๐๏ธ Part 2: The DEDUP Workflow
---
### ๐ Phase 0: Understand the Landscape
.pull-left[
**Read these first:**
.font-sm[
```bash
test/source/*.cpp # test files
test/CMakeLists.txt # build config
include/<project>/*.hpp # library headers
source/*.cpp # compiled sources
```
]
]
.pull-right[
**Note:**
- Test framework (doctest, GTest, Catch2)
- CMake `file(GLOB ...)` vs explicit lists
- Coverage flags: `-DENABLE_TEST_COVERAGE=ON`
]
**Check for pattern pairs:**
.font-sm[
| Pattern | Example | Suspect? |
|---------|---------|----------|
| `*_gaps.cpp` + `*_comprehensive.cpp` | `test_frac_gaps` + `test_frac_comprehensive` | ๐ด High |
| `*_basic.cpp` + `*_detailed.cpp` | `test_netlist` + `test_netlist_more` | ๐ก Medium |
| `*_raw.cpp` + `*.cpp` | `test_parametric_raw` + `test_parametric` | ๐ด High |
| `*_v2.cpp` + `*.cpp` | `test_optscaling2` + `test_optscaling` | ๐ก Medium |
]
---
### ๐ Phase 1: Detect Redundancy
**Step 1:** Read both files side-by-side. Group test scenarios by function:
```
test_frac_gaps.cpp test_coverage_gaps.cpp
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
gcd_recur(12, 8) = 4 gcd_recur(12, 8) = 4 โ SAME
gcd_recur(8, 12) = 4 gcd_recur(8, 12) = 4 โ SAME
gcd_recur(0, 8) = 8 gcd_recur(0, 8) = 8 โ SAME
gcd_recur(13, 5) = 1 gcd_recur(13, 5) = 1 โ SAME
gcd_recur(-12, 8) = 4 gcd_recur(-12, 8) = 4 โ SAME
keep_denom_positive(1, -2) โ -1,2 keep_denom_positive(1, -2) โ SAME
```
**Step 2:** Mark file as redundant if >90% of its scenarios appear elsewhere with same values.
**Step 3:** The remaining <10% (Fibonacci numbers, trivial edge cases) are safe to drop.
---
### ๐ Phase 2: Build Coverage Baseline
**Configure with GCC + gcov:**
```bash
cmake -S test -B build/cov \
-DENABLE_TEST_COVERAGE=ON \
-DCMAKE_CXX_COMPILER=g++
cmake --build build/cov
./build/cov/<TestExecutable>
```
**Generate header coverage:**
```bash
cd build/cov
gcov -o CMakeFiles/<Tests>.dir/source/<test>.cpp.o \
include/<project>/<header>.hpp
```
**Expected output:**
```
extfractions.hpp: 98.06% of 310 lines
pyfractions.hpp: 100% of 43 lines
Test cases: 138 | Assertions: 991 | Status: SUCCESS
```
**Record the uncovered lines** โ they must match after removal.
---
### ๐ฌ Uncovered Lines: The Real Test
**Coverage percentage can be misleading:**
```
98.06% โ 304/310 lines covered
```
**What are the 6 uncovered lines?** These must be **identical** before and after:
```bash
$ Get-Content extfractions.hpp.gcov | Select-String "^ #####:"
#####: 50: return _n < 0 ? -_n : _n;
#####: 162: this->_numer = -this->_numer;
#####: 163: this->_denom = -this->_denom;
#####: 820: ExtFraction result(other._denom * ..., 0);
#####: 821: result.normalize();
#####: 822: return result;
```
These are template branches never instantiated โ harmless. But if removal adds new red lines, **revert immediately**.
--
**Rule:** Coverage preserved โ same percentage. It means **same set of uncovered lines**.
---
### ๐๏ธ Phase 3โ5: Remove & Verify
**Remove:**
.font-sm[
```bash
# Delete the redundant file
rm test/source/test_frac_gaps.cpp
# CMake auto-detects the change (GLOB + CONFIGURE_DEPENDS)
cmake --build build/cov # rebuilds automatically
./build/cov/<TestExecutable> # re-run tests
```
]
.pull-left[
**Verify:**
.font-sm[
| Check | Requirement |
|-------|-------------|
| All tests pass | โ
Must pass |
| Per-header line coverage | **Exactly equal** before and after |
| Uncovered lines | **Identical set** of `#####:` lines |
| Test count decreased | Expected โ removed tests |
| Assertion count decreased | Expected โ removed assertions |
]
]
.pull-right[
**If coverage drops โ REVERT IMMEDIATELY.** The file was not redundant.
]
---
class: nord-light, middle, center
## ๐๏ธ Part 3: Real-World Case Studies
---
### ๐งช Case Study 1: fractions-cpp
**Redundancy found:** `test_frac_gaps.cpp` (207 lines) entirely subsumed by `test_coverage_gaps.cpp` (920 lines).
.pull-left[
**Result:**
| Metric | Before | After | ฮ |
|--------|--------|-------|---|
| Test cases | 138 | 133 | โ5 |
| Assertions | 991 | 932 | โ59 |
| extfractions.hpp | 98.06% | 98.06% | 0% |
]
.pull-right[
**Comparison โ all scenarios identical:**
.font-sm[
```
test_frac_gaps.cpp test_coverage_gaps.cpp
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
gcd_recur 12 values, all same 12 values, all same โ
keep_denom_positive 5 subcases 3 subcases (+more) โ
normalize return 5 subcases 5 subcases โ
reduce return 4 subcases 3 subcases โ
rvalue operators 4 subcases 4 subcases โ
```
]
]
**Uncovered lines:** 6 lines โ 6 lines (identical). โ
---
### ๐ฏ Case Study 2: ellalgo-cpp
**The fix โ 12 new tests written:**
.font-sm[
```cpp
TEST_CASE("ell1d: construction with bounds") { ... }
TEST_CASE("ell1d: update_central_cut with positive gradient") { ... }
TEST_CASE("ell1d: update_central_cut with negative gradient") { ... }
TEST_CASE("ell1d: update (deep cut) success") { ... }
TEST_CASE("ell1d: update (deep cut) NoSoln") { ... }
TEST_CASE("ell1d: update (deep cut) NoEffect") { ... }
...
```
]
.pull-left[
**No redundancy found** โ but coverage revealed a **GAP**:
**Before:**
```
Compiled sources:
ell1d.cpp: 0% (0/23) โ โ ๏ธ UNTESTED!
```
**Gap analysis:** `ell1d` is a 1D ellipsoid class with `update()` (deep cut) and `update_central_cut()` methods โ compiled `.cpp` file with zero test coverage.
]
.pull-right[
**Result:**
| Metric | Before | After | ฮ |
|--------|--------|-------|---|
| Test cases | 127 | 139 | +12 |
| ell1d.hpp | 0% | 100% | +100% |
| ell1d.cpp | 0% | 100% | +100% |
]
---
### ๐ Case Study 3: mywheel-cpp
**Removed:** 1 duplicate `TEST_CASE("Test Robin")` in `test_dllist.cpp` โ identical to `test_robin.cpp`.
| Metric | Before | After | ฮ |
|--------|--------|-------|---|
| Test cases | 23 | 22 | โ1 |
| Assertions | 2,500,272 | 2,500,271 | โ1 |
| All headers | 100% | 100% | 0% |
---
### ๐ด Case Study 4: digraphx-cpp
.pull-left[
**Two redundancies found:**
**1. Entire file:** `test_parametric_raw.cpp` (52 lines)
Every scenario duplicated by `test_parametric.cpp` โ same graph data, same `max_parametric()` call, same expected `r = 1.0`.
**2. Triplicate TEST_CASEs in `test_gaps_q.cpp`:**
Three identical blocks named `"MinParametricSolver with pick_one_only finds improving cycle"` โ literal copy-paste with identical graph, API, solver, and assertions.
]
.pull-right[
**Result:**
| Metric | Before | After | ฮ |
|--------|--------|-------|---|
| Test cases | 75 | 71 | โ4 |
| Assertions | 106 | 102 | โ4 |
| All pass? | โ
| โ
| โ |
]
---
### ๐ By the Numbers
| Project | Analyzed | Tests Before | Tests After | ฮ | Coverage Impact |
|---------|----------|:---:|:---:|:---:|:---:|
| **fractions-cpp** ๐งช | 10 files | 138 | 133 | **โ5** | 0% (preserved) |
| **mywheel-cpp** ๐ | 7 files | 23 | 22 | **โ1** | 0% (preserved) |
| **ellalgo-cpp** ๐ฏ | 23 files | 127 | 139 | **+12** | +100% (ell1d filled) |
| **digraphx-cpp** ๐ | 12 files | 75 | 71 | **โ4** | 0% (preserved) |
| **Total** | **52 files** | **363** | **365** | **+2** | **All โฅ 90%** |
---
class: nord-light, middle, center
## ๐ก Part 4: Key Rules & Summary
---
### ๐ก๏ธ Golden Rule: Coverage is the Truth
**NEVER remove a test file based on "looks similar."**
Always verify with coverage:
```diff
- "I think this file is redundant."
+ "gcov shows removing this file changes coverage by 0%."
```
**Why percentage isn't enough:**
```
98.06% of 310 lines โ line 50 uncovered
98.06% of 310 lines โ line 162 uncovered
Same percentage, DIFFERENT uncovered lines = test was covering unique code!
```
**Verdict:** Compare `#####:` lines in `.gcov` output, not just percentages.
---
### โ ๏ธ Why "100% Coverage" is Harmful
**The pursuit of 100% creates perverse incentives:**
.pull-left[
**โ What 100% mandates encourage:**
- Testing **getters and setters** (trivial, zero-risk)
- Adding **fake assertions** that pass vacuously
- **Avoiding** defensive branches because they're "untestable"
- Measuring **lines hit** instead of **paths validated**
- Celebrating the **number** while ignoring **quality**
"100% coverage means you tested the easy stuff and skipped the hard stuff."
]
.pull-right[
**โ
A better approach:**
.font-sm[
| Principle | Instead of 100% |
|-----------|----------------|
| **Risk-based** | Focus tests on code that can *fail dangerously* |
| **Boundary-focused** | Test edge cases, not happy paths |
| **Branch-aware** | Cover every `if/else` in critical algorithms |
| **Cost-aware** | 1 test covering 3 branches > 3 tests covering 1 branch |
| **Living at 70โ90%** | Leaves room for error-handling code, debug assertions |
]
]
---
**The law of diminishing returns:**
```text
First 70% coverage โ catches obvious bugs
70% โ 90% โ catches subtle edge cases
90% โ 100% โ exponentially more expensive per point
Last 2-3% โ often pure boilerplate or dead code
```
The right kind of 100% comes from **meaningful** tests covering all branches โ not from chasing a metric.
---
### ๐ The 10 Critical Rules
.pull-left[
**Must Do:**
1. **Coverage is truth** โ verify with gcov, not guesses
2. **Check uncovered lines** โ not just percentages
3. **Don't dedup property-based** โ RapidCheck is intentionally redundant
4. **Don't dedup different values** โ different inputs = different paths
5. **Rebuild from clean** โ stale `.gcda` files lie
]
.pull-right[
**Must Do (cont.):**
6. **Check CMake GLOB** โ `CONFIGURE_DEPENDS` auto-detects changes
7. **When in doubt, don't remove** โ false positive > false negative
8. **Hardcode expected values** โ don't recompute from code under test
9. **Test every branch** โ `if`, `else`, `switch` cases
10. **Prioritize compiled sources** โ `.cpp` files without tests first
]
--
.pull-left[
**Pattern to avoid:**
.font-sm[
```cpp
// BAD: Computed from code under test โ won't catch regressions
CHECK_EQ(result, add(a, b));
// GOOD: Hardcoded โ catches implementation changes
CHECK_EQ(result, 19);
```
]
]
---
### ๐ง Tooling Tips
**Finding untested compiled sources:**
.font-sm[
```bash
# List .cpp files without matching test_*.cpp
$source_files = Get-ChildItem source/*.cpp
foreach ($src in $source_files) {
$base = $src.Name -replace '\.cpp$'
$test = "test/source/test_$base.cpp"
if (-not (Test-Path $test)) {
Write-Host "โ ๏ธ UNTESTED: $($src.Name)"
}
}
```
]
**Quick coverage check:**
.font-sm[
```bash
gcov -o CMakeFiles/<Tests>.dir/source/<test>.cpp.o \
include/<project>/<header>.hpp \
| grep "Lines executed"
```
]
**Comparing uncovered lines:**
.font-sm[
```bash
# Before removal
Get-Content build/cov/header.hpp.gcov | Select-String "^ #####:"
# After removal โ must be IDENTICAL
Get-Content build/cov/header.hpp.gcov | Select-String "^ #####:"
```
]
---
### ๐ Key Takeaways
.pull-left[
**For DEDUP** ๐๏ธ
1. Look for `*_gaps.cpp` + `*_comprehensive.cpp` pattern
2. Compare **values**, not concepts
3. Build coverage baseline BEFORE removal
4. Check uncovered lines are identical AFTER
5. Revert if coverage drops โ even 0.01%
**For GAP-FILL** ๐งช
1. Check `source/*.cpp` against `test/source/*.cpp`
2. Read the source โ understand every branch
3. Write one test per `if`/`else`/`switch` case
4. **Hardcode expected values**
5. Annotate derivations for readability
]
.pull-right[
**The key insight:**
```text
A 30% reduction in test count
โ 30% faster CI
โ Same coverage
โ Better developer experience
```
**By the numbers:**
- **4 projects** analyzed
- **10 tests removed** (across 3 projects)
- **12 tests added** (1 project)
- **Coverage: 100% preserved or improved**
- **52 source files examined**
]
---
count: false
class: nord-dark, middle, center
## โ Questions?
---
count: false
class: nord-dark, middle, center
# ๐ Thank You
### Happy Testing!
**Slides**: [`luk036.github.io/proglang/dedup-cpp-tests-remark`](https://luk036.github.io/proglang/dedup-cpp-tests-remark.html)
**Skill**: `/dedup-cpp-tests` โ in OpenCode skills directory
**Repositories** (all tested):
- [`github.com/luk036/fractions-cpp`](https://github.com/luk036/fractions-cpp)
- [`github.com/luk036/ellalgo-cpp`](https://github.com/luk036/ellalgo-cpp)
- [`github.com/luk036/mywheel-cpp`](https://github.com/luk036/mywheel-cpp)
- [`github.com/luk036/digraphx-cpp`](https://github.com/luk036/digraphx-cpp)
@luk036 ๐จโ๐ป ยท 2026 ๐