0Pricing
PHP Academy · Lektion

Mutation Testing mit Infection

Messen, wie gut Ihre Tests wirklich sind

Mutation Testing mit Infection ist eine kostenlose PHP Academy-Lektion auf CoddyKit. Dies ist Lektion 4 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des PHP Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der PHP Academy-Kurs umfasst insgesamt 4 Lektionen.

Coverage täuscht

100 % Zeilen-Coverage wirkt beruhigend, beweist aber nur, dass Ihre Tests den Code ausgeführt haben – nicht, dass sie darin einen Fehler erkennen würden. Ein Test kann eine Zeile ausführen und trotzdem nichts Aussagekräftiges prüfen. Mutationstests messen das, worauf es wirklich ankommt: Würden Ihre Tests fehlschlagen, wenn der Code subtil beschädigt wäre? Infection ist dafür das Standardtool für PHP.

composer require --dev infection/infection

Die Grundidee: Mutanten

Infection nimmt Ihren abgedeckten Code und führt kleine Fehler ein – Mutanten. Es ersetzt > durch >=, + durch -, && durch ||, entfernt ein return usw. Anschließend führt es Ihre Tests für jeden Mutanten erneut aus:

  • Wenn ein Test fehlschlägt → wird der Mutant getötet (gut – Ihre Tests haben den Fehler erkannt).
  • Wenn alle Tests bestehen → hat der Mutant überlebt (schlecht – ein echter Fehler an dieser Stelle bliebe unbemerkt).

Ein Überlebender in Aktion

Betrachten Sie diese Funktion und einen schwachen Test. Infection würde >= zu > mutieren. Wenn kein Test die genaue Grenze prüft (der Betrag entspricht dem Schwellenwert), überlebt dieser Mutant – und zeigt eine nicht getestete Randbedingung auf.

<?php
function qualifiesForFreeShipping(float $total): bool
{
    return $total >= 50.0;   // Infection mutates >= to >
}

// Weak test only checks 100 and 10 -> never tests exactly 50.0
var_dump(qualifiesForFreeShipping(100.0)); // true
var_dump(qualifiesForFreeShipping(10.0));  // false
var_dump(qualifiesForFreeShipping(50.0));  // true  <-- the boundary the mutant exposes

Den Mutanten töten

Fügen Sie die Prüfung der Randbedingung hinzu, und der Mutant von >= zu > stirbt: Im mutierten Code ist 50.0 > 50.0 false, daher schlägt der Test fehl – genau das wollen wir. Mutationstests zeigen Ihnen buchstäblich, welche Assertions fehlen.

<?php
use PHPUnit\Framework\TestCase;

final class ShippingTest extends TestCase
{
    public function test_threshold_is_inclusive(): void
    {
        // Kills the >= -> > mutant
        self::assertTrue(qualifiesForFreeShipping(50.0));
    }
}

Konfiguration: infection.json

Infection wird über infection.json5 gesteuert. Sie legen fest, welche Verzeichnisse mutiert werden, wohin Protokolle geschrieben werden und welche Mindestwerte für den Score gelten müssen, damit CI erfolgreich ist. Verweisen Sie mit source.directories ausschließlich auf Ihren Produktionscode – niemals auf Ihre Tests.

{
  "source": {
    "directories": ["src"]
  },
  "logs": {
    "text": "build/infection.log",
    "html": "build/infection.html"
  },
  "mutators": {
    "@default": true
  },
  "minMsi": 80,
  "minCoveredMsi": 90
}

Die MSI-Metriken

Infection meldet den Mutation Score Indicator:

  • MSI = getötete / gesamte Mutanten. Wird durch nicht abgedeckten Code verschlechtert (diese Mutanten können nicht getötet werden).
  • Covered MSI = getötete Mutanten / Mutanten in abgedeckten Zeilen. Misst, wie gut Ihre Assertions dort sind, wo Sie testen.
  • Mutation Code Coverage = wie viel Code Infection überhaupt mutieren konnte.

Hohe Zeilen-Coverage bei gleichzeitig niedrigem Covered MSI ist das klassische Signal für Tests mit zu wenigen Assertions.

Infection effizient ausführen

Mutationstests sind teuer – die Testsuite wird für jeden Mutanten erneut ausgeführt. Zwei große Beschleunigungen sind: Tests mit --threads parallel ausführen und mithilfe einer Git-Diff-Filterung nur den Code mutieren, der von Ihrem aktuellen Branch verändert wurde – ideal für CI bei Pull Requests.

vendor/bin/infection --threads=max --git-diff-lines --git-diff-base=origin/main

Die Überlebenden analysieren

Der Wert steckt im Diff, den Infection für jeden überlebenden Mutanten ausgibt. Er zeigt die genaue Zeile und die Änderung, die Ihre Tests nicht erkannt haben. Betrachten Sie Überlebende als Aufgabenliste: Fügen Sie entweder die fehlende Assertion hinzu oder stellen Sie fest, dass der Mutant harmlos ist (ein äquivalenter Mutant).

- return $total >= 50.0;
+ return $total > 50.0;

# Mutant survived: no test asserts the inclusive boundary (total === 50.0)

Äquivalente Mutanten und Ignorieren

Einige Mutanten sind äquivalent – sie verändern den Code, aber nicht sein beobachtbares Verhalten, sodass kein Test sie jemals töten könnte. Diese Mutanten können nicht getötet werden und drücken den MSI zu Unrecht nach unten. Unterdrücken Sie bekannte False-Positive-Mutatoren gezielt für bestimmten Code, statt die Tests zu manipulieren, um ihnen nachzujagen.

{
  "mutators": {
    "@default": true,
    "Plus": {
      "ignore": ["App\\Math\\Statistics::variance"]
    }
  }
}

Wo sich Mutationstests lohnen

Da sie langsam sind, sollten Sie sie gezielt einsetzen:

  • Wenden Sie sie auf die zentrale Fachlogik an – Preisberechnung, Berechtigungen und Kalkulationen –, bei der stille Fehler teuer sind.
  • Machen Sie den Covered MSI der geänderten Zeilen zur Voraussetzung für PRs, nicht den des gesamten Repositorys.
  • Streben Sie nicht global 100 % an; abnehmender Nutzen und äquivalente Mutanten machen das ineffizient.
  • Nutzen Sie die Tests, um schwache Assertions aufzuspüren, und verbessern Sie anschließend die Tests – der Score ist ein Mittel, nicht das Ziel.

Für Geschwindigkeit braucht es Coverage-Daten

Infection mutiert nur Zeilen, die Ihre Tests tatsächlich abdecken, und verwendet daher die Coverage des Test-Runners erneut. Mit Xdebug ist das langsam; pcov ist für die Zeilen-Coverage deutlich schneller und der empfohlene Treiber für Mutationstests. Infection kann die Coverage auch selbst erzeugen oder einen Coverage-Bericht verwenden, den Sie bereits in CI erstellt haben.

# Faster mutation runs: use pcov instead of Xdebug for coverage
php -d pcov.enabled=1 vendor/bin/infection --threads=max

# Or reuse coverage already generated by your PHPUnit step:
vendor/bin/infection --coverage=build/coverage --skip-initial-tests

Schnelltest

Was sagt Ihnen ein überlebender Mutant?

Zusammenfassung

Sie haben gelernt, die Qualität von Tests zu messen, nicht nur ihre Anzahl:

  • Mutationstests führen kleine Fehler ein (Mutanten); getötet = erkannt, überlebt = eine Lücke.
  • Coverage zeigt die Ausführung; Covered MSI zeigt die Stärke der Assertions.
  • Konfigurieren Sie über infection.json5 mit MSI-Schwellenwerten; führen Sie die Tests für mehr Geschwindigkeit mit --threads und Git-Diff-Filterung aus.
  • Überlebende sind eine Aufgabenliste; achten Sie auf äquivalente Mutanten und ignorieren Sie diese bewusst.
  • Zielen Sie auf die zentrale Logik und machen Sie den MSI der geänderten Zeilen zur PR-Voraussetzung, statt global 100 % anzustreben.

Häufig gestellte Fragen

Ist die Lektion „Mutation Testing mit Infection“ kostenlos?

Ja — der vollständige Text von „Mutation Testing mit Infection“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des PHP Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der PHP Academy-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Mutation Testing mit Infection“?

Messen, wie gut Ihre Tests wirklich sind Du übst PHP Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um PHP Academy zu starten?

Keine Vorkenntnisse erforderlich. PHP Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 4 von 4.

Wie lange dauert die Lektion „Mutation Testing mit Infection“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser PHP Academy-Lektion Code schreiben und ausführen?

Ja. Jede PHP Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Der Test-Driven-Development-Workflow
  2. Mocking und Stubbing mit Mockery
  3. Integrations- und Funktionstests
  4. Mutation Testing mit Infection
← Zurück zu PHP Academy