Mutationstests in C++: Wenn 100% Codeabdeckung eine Lüge ist
\n\nSie sind stolz auf Ihre 95% Code Coverage? Vielleicht zu Unrecht. Codeabdeckung ist wie ein Schloss, das man nur durch Rütteln am Griff prüft. Mutationstests hingegen sind der Einbrecher, der wirklich versucht, den Safe zu knacken. In diesem Artikel schauen wir uns an, wie Mutationstests für C++ Ihre Tests von einer Formalität in eine echte Waffe verwandeln. Und ja, wir werden keine langweiligen Definitionen aus Wikipedia kopieren.
\n\nStellen Sie sich vor: Sie haben eine Funktion geschrieben, mit Tests abgedeckt, CI ist grün. Und dann geht ein Bug in Produktion. Ein bekanntes Problem? Meistens liegt das Problem nicht im Code, sondern in den Tests, die nicht das Richtige prüfen. Mutation testing für C++ löst genau dieses Problem – es zeigt, wie gefährlich Ihre Tests wirklich für Bugs sind.
\n\nWas sind Mutationstests und warum braucht ein C++-Entwickler sie?
\n\nMutationstests (oder Mutation Testing) sind eine Methode zur Bewertung der Testqualität, bei der kleine Änderungen (Mutationen) in den Quellcode eingefügt werden. Wenn die Tests nach der Mutation immer noch bestanden werden, sind sie schwach. Der Mutant hat überlebt. Wenn die Tests fehlschlagen, wurde der Mutant getötet – der Test funktioniert.
\n\nFür C++ ist das besonders relevant, weil:
\n- \n
- C++-Code oft komplexe Logik mit Zeigern, Templates und RAII enthält. \n
- Standardmetriken wie Line Coverage und Branch Coverage leicht zu täuschen sind. \n
- Die Kosten eines Bugs in C++ (insbesondere in Embedded, Finance, Game Engines) katastrophal sein können. \n
Ein einfaches Beispiel: Sie haben eine Funktion zur Altersprüfung:
\n\nbool is_adult(int age) {\n return age >= 18;\n}\n\nTest:
\n\nTEST(AdultTest, Positive) {\n EXPECT_TRUE(is_adult(20));\n}\n\nAbdeckung: 100% der Zeilen. Aber die Mutation age >= 18 → age > 18 wird nicht erkannt, weil 20 immer noch durchkommt. Der Mutant hat überlebt. Hätten Sie einen Test für is_adult(18) und is_adult(17) gehabt, hätten Sie den Bug gefunden. Mutationstests für C++ zwingen Sie dazu, solche Grenzfälle zu testen.
Wichtige Mutationsoperatoren in C++
\n\nTools für Mutationstests verwenden eine Reihe von Operatoren (Mutators). Hier sind die häufigsten für C++:
\n\n- \n
- Ersetzen arithmetischer Operatoren:
+→-,*→/\n - Ersetzen logischer Operatoren:
&&→||,==→!=\n - Ändern von Grenzbedingungen:
>=→>,<→<=\n - Entfernen von Ausdrücken: Funktionsaufruf wird durch eine Konstante ersetzt \n
- Ändern des Rückgabewerts:
return true→return false\n - Mutation von Schleifen: Änderung der Abbruchbedingung oder Schrittweite \n
Beispiel mit echtem Code:
\n\nint factorial(int n) {\n if (n <= 1) return 1;\n return n * factorial(n - 1);\n}\n\nMutation #1: n <= 1 → n < 1. Wenn der Test factorial(1) aufruft und 1 erwartet, die Mutation aber den Basisfall entfernt – fällt der Test nur, wenn Sie factorial(0) oder factorial(1) separat prüfen.
Mutation #2: return n * factorial(n - 1) → return n + factorial(n - 1). Wenn die Tests nur positive Zahlen abdecken, bemerken sie möglicherweise nicht die Ersetzung von Multiplikation durch Addition.
Genau solche Fälle deckt mutation testing in C++ auf.
\n\nTools für Mutationstests in C++
\n\nNicht alle Tools sind gleich nützlich. Hier sind die drei wichtigsten Akteure auf dem C++-Markt:
\n\n1. Mull
\nEin modernes, aktives Projekt. Arbeitet auf Basis eines LLVM-Passes. Unterstützt C++17/20, integriert sich mit Google Test, Catch2, doctest