CommunityCodierung & Entwicklunggithub.com

luk036/dedup-cpp-tests

Findet und entfernt redundante C++-Unit-Tests, schließt danach Abdeckungslücken durch Analyse von gcov-Berichten und gezielte Tests – verifiziert durch Vorher/Nachher-Vergleich der Codeabdeckung.

Was ist dedup-cpp-tests?

Diese KI-Fertigkeit automatisiert die Bereinigung und Optimierung von C++-Testsuiten. Sie identifiziert zunächst doppelte Testdateien, indem sie die von ihnen abgedeckten Code-Pfade und Eingabewerte miteinander vergleicht, und entfernt diejenigen, deren Szenarien zu über 90 % von anderen Dateien abgedeckt werden. Anschließend analysiert sie die Coverage-Berichte (gcov), um ungetestete oder schlecht getestete Code-Bereiche zu finden – insbesondere kompilierte .cpp-Dateien ohne zugehörige Testdatei sowie Bibliotheks-Header mit weniger als 90 % Zeilenabdeckung. Dafür generiert sie gezielt neue Testfälle, die jede Verzweigung (if/else, switch) und jede Randbedingung abdecken. Die Qualität der Änderungen wird durch einen präzisen Vorher-Nachher-Vergleich der gcov-Berichte sichergestellt: Nach der Deduplizierung muss die Abdeckung jedes Headers exakt gleich bleiben; nach dem Gap-Fill müssen die ungedeckten Zeilen (###: in .gcov) sinken. Die Fertigkeit unterstützt GCC/gcov, Clang/llvm-cov und MSVC (OpenCppCoverage) und folgt den Testkonventionen des jeweiligen Projekts (doctest, Catch2, Google Test). Sie eignet sich besonders für organisch gewachsene Testbasen, in denen mehrere Dateien die gleichen Code-Pfade testen, oder wenn vor einem Release die Abdeckung erhöht werden muss.

Funktioniert mit~Claude Code~Codex CLI~Cursor✓OpenCode
npx skills add https://github.com/luk036/agent-skills/tree/HEAD/skills/dedup-cpp-tests

Installed? Explore more Codierung & Entwicklung skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Was ich tue

Ich habe zwei Betriebsmodi, die zusammenwirken:

  1. DEDUP – Identifiziere und entferne redundante Testdateien/-szenarien, bei denen eine andere Datei bereits dieselben Codepfade mit denselben Eingabewerten abdeckt. Verifiziert durch gcov-Coverage-Vergleich (vorher vs. nachher).

  2. GAP-FILL – Analysiere nach der Deduplizierung den Coverage-Bericht, um ungetesteten oder schlecht getesteten Code (Header und kompilierte Quellen) zu finden, und schreibe gezielte Unit-Tests, um diese Lücken zu schließen. Verifiziert durch gcov-Coverage-Verbesserung (vorher vs. nachher).

Beide Modi teilen einen gemeinsamen Basis-Build- und Coverage-Messworkflow.

Wann ich verwendet werden soll

Für DEDUP:

  • Ihre C++-Testsuite ist organisch gewachsen und enthält mehrere Dateien, die denselben Code abdecken
  • Gap-Filler-Testdateien (*_gaps.cpp) wurden neben umfassenden Testdateien (*_comprehensive.cpp) hinzugefügt
  • Sie haben eine Codebasis mit doppelten Testszenarien übernommen
  • Sie möchten die CI-Testzeit verkürzen, ohne Coverage zu verlieren

Für GAP-FILL:

  • Sie haben kompilierte .cpp-Quelldateien ohne zugehörige Testdatei
  • Der Coverage-Bericht zeigt Header mit <90% Zeilenabdeckung
  • Sie haben gerade redundante Tests entfernt und möchten die gewonnene Testzeit in das Füllen von Lücken investieren
  • Sie müssen die Coverage vor einem Release-Termin erhöhen

Verwenden Sie mich NICHT, wenn:

  • Tests parametrisiert/generativ dasselbe Property testen (das ist beabsichtigte Redundanz)
  • Das Projekt hat keine Coverage-Tooling eingerichtet
  • Sie nur Tests hinzufügen müssen, ohne zuerst auf Redundanz zu prüfen

Wie ich arbeite

Phase 0: Verstehen der Testlandschaft

Lesen Sie das Testverzeichnis und die CMakeLists.txt:

test/source/*.cpp
test/CMakeLists.txt
include/<project>/*.hpp
source/*.cpp              # kompilierte Quellen

Prüfen Sie auf:

  • Musterpaare: *_gaps.cpp + *_comprehensive.cpp, *_basic.cpp + *_detailed.cpp
  • Kompilierte .cpp-Dateien in source/ ohne zugehörige test_*.cpp
  • Header in include/ ohne dedizierte Testdatei
  • Test-Framework (doctest, Catch2, Google Test usw.) und CMake-Globbing

Hinweis:

  • Test-Framework und verwendete Makros (TEST_CASE, CHECK_EQ usw.)
  • Ob CMakeLists.txt file(GLOB ...) oder explizite Quelllisten verwendet
  • Das Coverage-Flag-Muster (normalerweise -DENABLE_TEST_COVERAGE=ON)

Phase 1: Redundanz auf DATEI-Ebene erkennen

Vergleichen Sie für jede Kandidatendatei ihre Testszenarien mit jeder anderen Testdatei in der Suite.

Konkrete Vergleichsmethode:

  1. Lesen Sie beide Dateien nebeneinander. Gruppieren Sie Testszenarien nach dem Code, den sie ausführen:

    • Freie Funktionen (gcd, lcm usw.)
    • Klassenmethoden (Konstruktoren, Operatoren, Zugriffsmethoden, Mutatoren)
    • Template-Instanziierungen und Spezialisierungen
    • Randfallabdeckung (Null, Unendlich, NaN, Grenzwerte)
  2. Prüfen Sie auf Wertduplikation. Wenn beide Dateien dieselbe Funktion mit identischen Werten testen (z.B. gcd_recur(12, 8) = 4 in beiden), ist dieses Szenario redundant. Kleinere Variationen (Fibonacci-Zahlen in einer, aber nicht in der anderen) sind NICHT redundant – das sind einzigartige Eingaben.

  3. Markieren Sie eine Datei als "redundant", wenn >90% ihrer Testszenarien in einer anderen Datei mit gleicher oder umfassenderer Abdeckung erscheinen. Die verbleibenden <10% einzigartigen Szenarien (triviale Variationen) können gefahrlos entfernt werden.

Phase 2: Basislinie erstellen (VOR der Entfernung)

Erstellen Sie die Testsuite mit Coverage-Instrumentierung:

ToolKonfiguration
GCC + gcovcmake -S test -B build/cov -DENABLE_TEST_COVERAGE=ON -DCMAKE_CXX_COMPILER=g++ -G "Unix Makefiles"
Clang + llvm-covcmake -S test -B build/cov -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_FLAGS="-fprofile-instr-generate -fcoverage-mapping" -G "Ninja"
MSVCVerwenden Sie OpenCppCoverage oder die integrierte Coverage von VS Enterprise

Dann erstellen und ausführen:

cmake --build build/cov
./build/cov/<TestExecutable>

Coverage für Bibliotheksheader generieren

cd build/cov

# Für jeden Bibliotheksheader: gcov gegen ein Testobjekt ausführen, das ihn #includet
gcov -o CMakeFiles/<Tests>.dir/source/<test_file>.cpp.o <path>/include/<project>/<header>.hpp

# Für jede kompilierte Quelle: gcov gegen das Bibliotheksobjekt ausführen
gcov -o _deps/<project>-build/CMakeFiles/<Project>.dir/source/<source>.cpp.o <path>/source/<source>.cpp

Basis-Metriken aufzeichnen:

Header:
  array_like.hpp:    100%  (28/28)
  bpqueue.hpp:       100%  (73/73)
  ...
Kompilierte Quellen:
  ell1d.cpp:           0%   (0/23)   ← UNGETESTET!
  ldlt_mgr.cpp:      100%  (10/10)
  ...

Testfälle: N  |  Assertions: M  |  Status: SUCCESS

Notieren Sie auch die genauen ungedeckten Zeilen für jede Datei (die #####:-Zeilen im .gcov-Output).

Phase 3: Redundante Dateien entfernen (DEDUP-Modus)

Remove-Item -Force test/source/test_*_gaps.cpp    # PowerShell
git status test/
git diff --stat

Phase 4: Neu erstellen und NACH-DEDUP-Metriken sammeln

# Neu konfigurieren (CMake GLOB erkennt möglicherweise fehlende Dateien)
cmake -S test -B build/cov -DENABLE_TEST_COVERAGE=ON -DCMAKE_CXX_COMPILER=g++ -G "Unix Makefiles"
cmake --build build/cov
./build/cov/<TestExecutable>

Vergleichen Sie die Coverage mit der Basislinie. Wenn die Coverage eines Headers sinkt oder sich die ungedeckten Zeilen ändern, sofort rückgängig machen – die Datei war NICHT redundant.

Bewertungskriterien für DEDUP:

PrüfungAnforderung
Alle Tests bestehenMuss bestehen (sowohl vorher als auch nachher)
Zeilenabdeckung pro HeaderExakt gleich vorher und nachher
Ungedeckte ZeilenIdentische Menge ungedeckter Zeilen
Testanzahl verringertErwartet – Testfälle entfernt
Assertion-Anzahl verringertErwartet – Assertions entfernt

Phase 5: Coverage-Lücken analysieren (GAP-FILL-Modus)

Wenn nach der Deduplizierung (oder bei einem Neuanfang) der Coverage-Bericht Lücken zeigt, identifizieren Sie ungetesteten Code:

Lückentyp A: Kompilierte Quelle ohne Abdeckung

Prüfen Sie source/*.cpp gegen test/source/*.cpp. Jede .cpp in source/ ohne passende test_*.cpp ist wahrscheinlich ungetestet.

# Quelldateien ohne Testabdeckung finden
Get-ChildItem source/*.cpp | ForEach-Object {
    $base = $_.Name -replace '\.cpp$'
    $test = "test/source/test_$base.cpp"
    if (-not (Test-Path $test)) { Write-Host "UNGETESTET: $($_.Name)" }
}

Lückentyp B: Bibliotheksheader mit niedriger Abdeckung

Sehen Sie sich den gcov-Bericht für Header mit <90% Zeilenabdeckung oder "keine ausführbaren Zeilen" an. Template-lastige Header können die Abdeckung der .cpp-Datei zuschreiben – prüfen Sie beide.

Lückentyp C: Funktionen/Verzweigungen innerhalb abgedeckter Dateien

Die ungedeckten Zeilen (#####: in gcov) innerhalb teilweise abgedeckter Dateien zeigen bestimmte Verzweigungen, die nicht ausgeführt werden. Dies sind die Ziele mit dem höchsten Wert.

Phase 6: Gezielte Tests schreiben

6a: Ungetesteten Quellcode lesen

Lesen Sie die Quelldatei vollständig, um zu verstehen:

  • Die Klassen-/Funktions-API (Konstruktoren, Methoden, Parameter)
  • Alle bedingten Verzweigungen (if/else, switch-Fälle, frühe Rückgaben)
  • Randfälle (Null-/Nulleingaben, Grenzwerte, Fehlerbedingungen)
  • Das mathematische oder algorithmische Verhalten

6b: Testfälle entwerfen, die jede Verzweigung abdecken

Für jede Methode die benötigten Testszenarien auflisten:

MethodeBedingungEingabeErwartetes Verhalten
update(cut)beta > taug=1, beta=10 mit _r=5Gibt NoSoln zurück, Zustand unverändert
update(cut)beta < -taug=1, beta=-10 mit _r=5Gibt NoEffect zurück, Zustand unverändert
update(cut)`beta<= tau, g>0`
update(cut)`beta<= tau, g<0`
update_central_cut(cut)g > 0g=1, beta=0Halbiert _r, verschiebt Zentrum nach links
update_central_cut(cut)g < 0g=-1, beta=0Halbiert _r, verschiebt Zentrum nach rechts

Randfälle einschließen:

  • Intervalle mit Nullbreite
  • Grenzwerte (beta == tau, beta == -tau)
  • Null-Gradient (falls zutreffend)
  • Wiederholte/verkettete Operationen zur Überprüfung von Zustandsübergängen

6c: Testdatei schreiben

Schreiben Sie eine neue test_<module>.cpp in test/source/ nach den bestehenden Testmustern des Projekts:

#define DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
#include <doctest/doctest.h>

#include <ellalgo/<module>.hpp>   // zu testender Header
#include <ellalgo/ell_config.hpp>  // falls für Typdefinitionen benötigt
#include <utility>                 // falls für std::pair benötigt

TEST_CASE("<Module>: Konstruktion") {
    auto x = <Module>(...);
    CHECK_EQ(x.some_method(), expected_value);
}

TEST_CASE("<Module>: Methode mit Erfolgspfad") {
    auto x = <Module>(...);
    auto status = x.update(...);
    CHECK_EQ(status, CutStatus::Success);
    CHECK_EQ(x.state(), expected_state);
}

TEST_CASE("<Module>: Methode mit Fehlerpfad") {
    auto x = <Module>(...);
    auto status = x.update(...);  // löst beta > tau-Zweig aus
    CHECK_EQ(status, CutStatus::NoSoln);
}

TEST_CASE("<Module>: Randfälle") {
    // Nullbreiten-Intervall, Grenzbedingungen usw.
}

TEST_CASE("<Module>: Verkettete Operationen") {
    // Mehrere Updates in Folge
}

Regeln für Testwerte:

  • Verwenden Sie hartcodierte erwartete Werte (nicht aus demselben zu testenden Code berechnet)
  • Kommentieren Sie jeden erwarteten Wert mit der mathematischen Herleitung (z.B. // tau = |5*1| = 5, bound = 5 - 2/1 = 3, _r = half(3-0) = 1.5, _xc = 1.5)
  • Testen Sie jeden if-Zweig mindestens einmal
  • Testen Sie jeden switch-Fall mindestens einmal
  • Testen Sie mindestens eine verkettete/sequentielle Operation zur Überprüfung der Zustandspersistenz

Phase 7: Neu erstellen und NACH-GAP-FILL-Metriken sammeln

cmake -S test -B build/cov -DENABLE_TEST_COVERAGE=ON -DCMAKE_CXX_COMPILER=g++ -G "Unix Makefiles"
cmake --build build/cov
./build/cov/<TestExecutable>

# Coverage-Berichte neu generieren
gcov -o CMakeFiles/<Tests>.dir/source/test_<module>.cpp.o <path>/include/<project>/<module>.hpp
gcov -o _deps/<project>-build/CMakeFiles/<Project>.dir/source/<module>.cpp.o <path>/source/<module>.cpp

Phase 8: Coverage-Verbesserung melden

| Metrik | Vorher | Nachher | Δ |
|--------|--------|---------|---|
| Testfälle | N | N' | +Δ |
| Assertions | M | M' | +Δ |
| <module>.hpp | XX% | 100% | +Δ |
| <module>.cpp (kompiliert) | 0% | 100% | +100% |
| Alle anderen Dateien | ≥90% | ≥90% | 0% |

Coverage-Lücken geschlossen: ✅

KRITISCHE REGELN

Regel 1: Coverage ist die Wahrheit, nicht die Testanzahl

Eine Testdatei ist nur dann redundant, wenn ihre Entfernung die Zeilenabdeckung jedes Bibliotheksheaders unverändert lässt. Wenn die Coverage sinkt, hat die Datei einzigartige Codepfade abgedeckt. Sofort rückgängig machen.

Beim Hinzufügen von Tests ist ein neuer Test nur erfolgreich, wenn er die Coverage der Zieldatei erhöht. Wenn die Coverage der Zieldatei nicht steigt, übt der Test den Code nicht aus – überprüfen Sie die Testlogik.

Regel 2: Ungedeckte Zeilen prüfen, nicht nur den Prozentsatz

Zwei Läufe können "98,06%" anzeigen, aber unterschiedliche ungedeckte Zeilen haben. Vergleichen Sie immer die exakte Menge der #####:-Zeilen in der .gcov-Datei:

Get-Content build/cov/foo.hpp.gcov | Select-String -Pattern "^    #####:"

Diese müssen vorher/nachher der Deduplizierung identisch sein. Für Gap-Fill müssen die #####:-Zeilen der Zieldatei abnehmen.

Regel 3: Deduplizieren Sie KEINE eigenschaftsbasierten Tests

Eigenschaftsbasierte / generative Tests (z.B. RapidCheck) sind absichtlich redundant. Sie überprüfen Invarianten mit zufälligen Eingaben. Diese sollten NICHT entfernt werden, selbst wenn sie sich mit beispielbasierten Tests überschneiden.

Regel 4: Deduplizieren Sie KEINE Tests mit unterschiedlichen Werten

Zwei Tests für denselben Operator mit unterschiedlichen Eingabewerten üben unterschiedliche Codepfade aus. Nur entfernen, wenn Eingabewerte und erwartete Ausgaben identisch sind.

Verwenden Sie beim Schreiben neuer Tests ANDERE Eingabewerte als in bestehenden Tests. Das Duplizieren derselben Werte erhöht die Coverage nicht.

Regel 5: Immer aus sauberem Zustand neu erstellen

.gcda-Dateien werden von gcov verbraucht und werden nach Quell- oder Binäränderungen ungültig. Vor der Messung immer neu erstellen und ausführen:

cmake --build build/cov --clean-first   # oder von Grund auf neu erstellen
./build/cov/<TestExecutable>

Regel 6: CMake GLOB-Verhalten prüfen

Wenn CMakeLists.txt file(GLOB ...) mit CONFIGURE_DEPENDS verwendet, erkennt CMake automatisch hinzugefügte/entfernte Dateien beim Neuerstellen (CMake 3.12+). Bei älterem CMake nach dem Hinzufügen oder Entfernen von Testdateien manuell neu konfigurieren.

Stellen Sie beim Schreiben einer neuen Testdatei sicher, dass CMake sie aufnimmt:

  • Suchen Sie nach test_<module>.cpp in der Build-Ausgabe
  • Wenn nicht vorhanden, neu konfigurieren: cmake -S test -B build/cov

Regel 7: Erwartete Werte hartcodieren, nicht neu berechnen

Berechnen Sie beim Schreiben von Tests NIEMALS erwartete Werte mit demselben zu testenden Code. Leiten Sie sie immer von Hand ab und hinterlegen Sie sie als Literale. Dies fängt Regressionen ab, bei denen die Implementierung geändert wird, der Test aber trotzdem besteht, weil beide Seiten denselben fehlerhaften Code verwenden.

Kommentieren Sie komplexe Herleitungen:

// tau = |5*1| = 5, bound = 5 - 2/1 = 3
// g>0 also u=3, l=5-5=0
// _r = half_nonnegative(3-0) = 1.5, _xc = 0 + 1.5 = 1.5
CHECK_EQ(e.xc(), 1.5);

Regel 8: Jede Verzweigung testen

Für jede Funktion mit bedingter Logik alle Verzweigungen auflisten und mindestens einen Test pro Verzweigung schreiben:

VerzweigungTestszenario
if (beta > tau)Kein Lösungspfad
if (beta < -tau)Kein Effektpfad
else (Erfolg)Erfolgspfad
g > 0 innerhalb ErfolgPositiver Gradient
g < 0 innerhalb ErfolgNegativer Gradient
Grenze beta == tauErfolg trotzdem (≤)
Grenze beta == -tauHängt von < vs ≤ ab

Regel 9: Kompilierte Quellen gegenüber Headern priorisieren

Kompilierte .cpp-Dateien haben mit größerer Wahrscheinlichkeit eine NULL-Abdeckung, da Header-Only-Template-Code oft durch andere Tests beiläufig ausgeführt wird. Priorisieren Sie das Testen:

  1. source/*.cpp ohne zugehörige test_*.cpp → höchste Priorität
  2. Header mit <90% Zeilenabdeckung → mittlere Priorität
  3. Ungedeckte Verzweigungen in ansonsten abgedeckten Dateien → niedrigere Priorität

Regel 10: Projekt-Testkonventionen einhalten

Bevor Sie Tests schreiben, studieren Sie 2-3 bestehende Testdateien, um Folgendes zu überprüfen:

  • Eingerückt Stil und Formatierung
  • Testfall-Benennungskonventionen (snake_case, CamelCase usw.)
  • Verwendete Makros (CHECK, REQUIRE, CHECK_EQ, CHECK_CLOSE usw.)
  • Ausnahmebehandlung (werden Ausnahmen erwartet oder nicht?)
  • Test-Fixture-Verwendung (Klassen mit SetUp/TearDown usw.)
  • Test-Framework-Header (#include-Header)
  • Projekt-spezifische Test-Utilities oder Matcher

SPEZIFIKATION FÜR BENUTZERDEFINIERTE DATEIEN

- test/source/test_<module>.cpp

Standard-Unit-Testdatei für eine Quelldatei oder einen Header. Folgt den Test-Framework-Konventionen des Projekts (doctest, Catch2, Google Test usw.).

Enthält:

  • #define-Anpassungen für das Test-Framework (z.B. DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS)
  • #include-Anweisungen für den zu testenden Header und das Test-Framework
  • TEST_CASE-Blöcke, die jede öffentliche Methode/Funktion und deren Verzweigungen abdecken
  • Hartcodierte erwartete Werte mit Kommentaren zur mathematischen Herleitung

- test/CMakeLists.txt

Muss die neue Testdatei enthalten (entweder über file(GLOB ...) oder eine explizite add_executable-Liste). Wenn das Projekt file(GLOB ...) verwendet, stellen Sie sicher, dass CONFIGURE_DEPENDS gesetzt ist (CMake 3.12+), oder konfigurieren Sie nach dem Hinzufügen der Datei neu.

VERWANDTE FÄHIGKEITEN

luk036/analyze-cpp-code — Liest und fasst C++-Code aus einem Repository zusammen
luk036/read-dir — Liest Dateien rekursiv aus einem Verzeichnis
luk036/run-command — Führt Shell-Befehle im Repository aus

Individual skills in this repo

This repo contains 3 individual skills — each has its own dedicated page.

Verwandte Skills