Einleitung: Warum ist 100% Code Coverage eine Illusion?
\nHaben Sie sich jemals über einen PHPUnit-Bericht gefreut, in dem „Code Coverage: 98%“ stand? Und dann haben Sie ein Release ausgerollt, und in der Produktion trat ein kritischer Fehler auf. Kommt Ihnen bekannt vor? Das Problem ist, dass die klassische Abdeckung (Line/Statement Coverage) nur misst, welche Codezeilen ausgeführt wurden. Sie prüft nicht, ob Ihre Tests die Logik tatsächlich überprüfen.
\nStellen Sie sich vor, Sie schreiben einen Test, der prüft, ob eine Funktion true zurückgibt. Die Funktion selbst gibt aber immer true zurück, selbst wenn man sie kaputt macht. Der Test wird bestanden, die Abdeckung beträgt 100%, aber den Fehler werden Sie nicht finden. Genau hier kommt Mutation Testing in PHP ins Spiel – eine Technik, die das herkömmliche Verständnis von Testen auf den Kopf stellt.
\nIn diesem Artikel werden wir uns ansehen, was Mutationstests sind, wie sie anhand von echtem PHP-Code funktionieren und wie Sie sie mit der Bibliothek Infection in Ihr Projekt integrieren können.
\n\nWas ist Mutation Testing? (Und warum es keine Magie ist)
\nMutation Testing (oder Mutationstests) ist eine Methode zur Bewertung der Testqualität, bei der kleine, absichtliche Änderungen (Mutationen) am Quellcode vorgenommen werden. Wenn der Test nach der Änderung fehlschlägt – ist der Mutant getötet, der Test funktioniert. Wenn der Test grün bleibt – hat der Mutant überlebt, was bedeutet, dass Ihr Test nichts überprüft.
\nEine einfache Analogie: Sie sind ein Wachmann, Ihre Tests sind Kameras. Das Mutation Testing schickt „Eindringlinge“ (Mutanten) ins Gebäude. Wenn die Kamera (der Test) den Eindringling bemerkt – funktioniert sie. Wenn der Eindringling ungestört herumläuft – ist die Kamera nutzlos.
\nIn PHP ist das wichtigste Werkzeug dafür Infection – eine Bibliothek, die PHPUnit für jeden Mutanten ausführt und die Ergebnisse analysiert.
\n\nWie sieht das in der Praxis aus?
\nAngenommen, Sie haben eine einfache Rechner-Klasse:
\n<?php\nclass Calculator\n{\n public function add(int $a, int $b): int\n {\n return $a + $b;\n }\n}\nUnd einen dazugehörigen Test:
\n<?php\nuse PHPUnit\\Framework\\TestCase;\n\nclass CalculatorTest extends TestCase\n{\n public function testAdd()\n {\n $calc = new Calculator();\n $result = $calc->add(2, 3);\n $this->assertEquals(5, $result);\n }\n}\nDas Tool Infection erstellt einen Mutanten, indem es + durch - ersetzt:
return $a - $b;\nDann führt es den Test aus. Wenn der Test fehlschlägt – ist der Mutant getötet. Wenn der Test bestanden wird (und er wird bestanden, weil Sie nur 2+3 und nicht 2-3 prüfen) – hat der Mutant überlebt. Das ist ein Signal: Ihr Test ist nicht gut genug.
\n\nInstallation von Infection und erster Start
\nZunächst installieren wir Infection über Composer:
\ncomposer require --dev infection/infection\nDann erstellen wir eine Konfigurationsdatei infection.json (oder führen Sie vendor/bin/infection für die interaktive Einrichtung aus).
Beispiel einer minimalen Konfiguration:
\n{\n "source": {\n "directories": [\n "src"\n ]\n },\n "phpUnit": {\n "configDir": "."\n },\n "mutators": {\n "@default": true\n }\n}\nStarten Sie das Mutation Testing:
\nvendor/bin/infection --min-msi=100\nWobei --min-msi=100 bedeutet, dass wir möchten, dass 100% der Mutanten getötet werden (ein idealer, aber selten erreichbarer Wert). Infection erstellt einen Bericht im HTML-Format (Ordner infection.html), in dem überlebende Mutanten rot markiert sind.
Arten von Mutationen in PHP (mit Beispielen)
\nInfection unterstützt Dutzende von Mutatoren. Hier sind die häufigsten, denen Sie begegnen werden:
\n\n1. Mathematische Operatoren
\nErsetzung von + durch -, * durch /, % durch * usw.
// Ursprünglicher Code\nreturn $price * $quantity;\n\n// Mutant\nreturn $price / $quantity;