Schulung · Vertiefung
Testen jenseits der Beispiele
Property-Based Testing, Mutation Testing und Fuzzing: Tests, die finden, woran niemand gedacht hat
- Umfang 6 Sitzungen à 2 Stunden
- Format Live über Zoom
- Sprache Deutsch
- Preis pro Person 1.199 €
Worum es geht
Coding Agents erzeugen Code und Tests schneller als je zuvor. Die Tests sind grün, die Code Coverage ist hoch. Aber was ist mit den Fällen, an die weder du noch der Agent gedacht habt? Und würden diese Tests einen Fehler überhaupt bemerken?
Beispielbasierte Tests prüfen für eine bekannte Eingabe eine bekannte Ausgabe. Das funktioniert, solange an alle relevanten Fälle gedacht wurde. Diese Schulung geht mit drei Techniken darüber hinaus:
- Eigenschaften statt Beispiele. Property-Based Testing prüft Aussagen über alle Eingaben und findet Grenzfälle, die du nie aufgeschrieben hättest.
- Absichtliche Fehler. Mutation Testing baut Fehler in deinen Code ein und zeigt dir, welche davon deine Tests nicht bemerken.
- Unerwartete Eingaben. Fuzzing konfrontiert deinen Code mit Eingaben, mit denen niemand gerechnet hat, und findet, wo er abstürzt.
Property-Based Testing lernst du mit der neuen Unterstützung in PHPUnit 14, von dem Menschen, der sie entworfen hat. Fuzzing lernst du mit rattle, Sebastians Fuzzer für PHP, der die Properties einer PHPUnit-Testsuite so fuzzt, wie sie sind. In der letzten Sitzung setzt du die Techniken an deinem eigenen Code ein, kombinierst sie und tauschst dich mit den anderen aus: Was hat wo funktioniert, und was nicht?
Die Techniken hängen nicht an PHP. Sebastian vermittelt sie an PHP-Beispielen mit den Werkzeugen des PHP-Ökosystems, und die Erkenntnisse nimmst du in jede Codebasis mit, egal ob PHP, C#, Go, Java, Python, Rust oder TypeScript.
So arbeitest du
Jede Sitzung beginnt mit einer kurzen Vorführung, den größten Teil der Zeit übst du selbst, allein oder zu zweit. Durch die ersten fünf Sitzungen ziehen sich drei Klassen mit absichtlich eingebauten Fehlern: IniParser, DiscountCalculator und ListCodec. Ihre Tests sind grün, ihre Coverage ist fast vollständig, und doch findet jede Technik etwas, das die anderen übersehen. So zeigen die überlebenden Mutanten im ListCodec in der dritten Sitzung auf den Fehler, den Property-Based Testing in der ersten gefunden hat, und die Roundtrip-Eigenschaft für den IniParser aus der zweiten Sitzung wird in der fünften zum Orakel für den Fuzzer.
Jede Sitzung schließt mit einem Blick über PHP hinaus und nennt die entsprechenden Werkzeuge anderer Sprachen.
Ablauf
-
Sitzung 1: Property-Based Testing: Woran niemand denkt
Was übersehen deine Beispiele?
- Ein Beispiel prüfen oder eine Aussage über alle Eingaben treffen: Data Provider oder Property?
- Eine Property ist eine Testmethode mit
#[Property]: PHPUnit erzeugt ihre Argumente aus den Typen der Parameter, auch aus PHPDoc-Typen wielist<int>odernon-empty-string, aus Enumerationen und über Konstruktoren aus Objekten - Jedes Beispiel ist ein vollständiger Test mit eigenem
setUp(), eigenen Test Doubles und eigenen Erwartungen - Shrinking: vom zufälligen zum minimalen Gegenbeispiel, das PHPUnit als PHP-Code ausgibt
- Reproduzieren: der Seed des Testlaufs mit
--random-seed, gemerkte Gegenbeispiele und das Gegenbeispiel als Regressionstest mit#[TestWith] - Am
ListCodecin drei Akten: ein Loch in der Spezifikation, ein echter Fehler, der Fix
Ergebnis: Eine Roundtrip-Property für den
ListCodec, die einen Fehler findet, und ihr Gegenbeispiel als Regressionstest, der den fehlschlagenden Lauf reproduziert -
Sitzung 2: Eigenschaften finden
Welche Aussagen gelten für alle Eingaben deines Codes?
- Muster für Eigenschaften: Roundtrip, Invariante, Idempotenz, Monotonie, Referenzorakel und metamorphe Beziehungen, auch als Ausweg aus dem Orakel-Problem, wenn niemand die richtige Antwort kennt
- Wenn der Typ nicht genug sagt: eigene Generatoren, Generatoren für Domänenobjekte, die nicht über ihren Konstruktor entstehen dürfen, und abhängige Werte
- Vorbedingungen, und warum viele verworfene Beispiele ein Warnsignal sind
- Prüfen, was erzeugt wird: Statistiken, die zeigen, ob die Beispiele die interessanten Fälle treffen
- Zustand: Ein Zustandsautomat vergleicht das System nach jedem Schritt mit einem möglichst einfachen Modell
- Grenzen und Kosten im Alltag: Seiteneffekte, langsame Funktionen, die Zahl der Beispiele, mehr Beispiele im nächtlichen Lauf und CI-Läufe, die beim Wiederholen dasselbe Ergebnis liefern
Ergebnis: Eine Monotonie-Eigenschaft für den
DiscountCalculatormit eigenem Generator und Statistiken darüber, ob ihre Beispiele die interessanten Fälle treffen, und die Roundtrip-Eigenschaft für denIniParser, auf der die fünfte Sitzung aufbaut -
Sitzung 3: Mutation Testing: Der blinde Fleck
Würden deine Tests einen echten Fehler bemerken?
- Das Coverage-Paradox: 100 % Coverage, und trotzdem bleibt jeder Grenzwertfehler unentdeckt
- Wie Mutation Testing funktioniert: Mutationsoperatoren, ein Mutant pro Lauf, die Testsuite als Richter
- Begriffe: killed, escaped, timeout, uncovered; Mutation Score Indicator (MSI) und Covered MSI
- Infection mit PHPUnit ausführen, Konsolenausgabe und Bericht lesen
- Mutanten töten, etwa bei Grenzwerten und Rundung: Ein Mutant stirbt an einer schärferen Erwartung, nicht an einem weiteren Testlauf
- Zurück zum
ListCodec: Seine überlebenden Mutanten zeigen auf den Fehler aus der ersten Sitzung, den die beispielbasierten Tests nicht bemerkt hätten
Ergebnis: Ein Lauf von Infection am
IniParser, dessen Ausgabe du liest, und Tests, die seine überlebenden Mutanten töten -
Sitzung 4: Mutation Testing im Alltag
Welche Mutanten lohnen sich, und wie kommt Mutation Testing in die CI?
- Der letzte Überlebende: Ein äquivalenter Mutant zeigt auf eine Designentscheidung
- Töten, ausnehmen oder den Entwurf ändern: welche Mutanten sich nicht lohnen, und wie du sie bewusst und sichtbar ausnimmst
- Der MSI als Kennzahl: Trend statt Schwelle, Covered MSI statt MSI, und warum 100 % kein Ziel ist
- Laufzeit: Testauswahl über Coverage, Parallelisierung und eine schnelle Suite als Voraussetzung
- In der CI nur geänderten Code mutieren, mit
--min-msials Gate und dem vollen Lauf in der Nacht - Mutation Testing als Prüfstein für generierte Tests: Wie viele Mutanten tötet die Suite, die ein Agent geschrieben hat?
Ergebnis: Eine Entscheidung für jeden Überlebenden, den du aus deiner Codebasis mitbringst, ein äquivalenter Mutant als Feedback zum Entwurf, eine MSI-Politik und eine CI-Konfiguration, die nur geänderten Code mutiert
-
Sitzung 5: Fuzzing: Womit niemand rechnet
Ist dein Code auch robust?
- Korrekt ist nicht robust: definierte Fehlermodi statt Warnungen
- Wie coverage-gesteuertes Fuzzing mit rattle funktioniert: Korpus, Mutation, Feedback
- Das Target als Vertrag über Fehler: erwartete Exceptions mit
expect(), die maximale Länge der Eingabe und das Dictionary, das rattle aus den Vergleichen in deinem Code gewinnt - Die Triage-Pipeline: rattle reproduziert und minimiert jeden Fund, du behebst den Fehler, und
rattle regressspielt den Fund in der CI als Regressionstest ab - Fuzzing und Property-Based Testing:
rattle fuzz --phpunitfuzzt die Roundtrip-Eigenschaft aus der zweiten Sitzung so, wie sie ist, und ein Fund hat dieselbe Fehlersignatur wie ein Gegenbeispiel von PHPUnit - Sicherheitsperspektive: alles fuzzen, was Bytes von außen anfasst, und Path Traversal, Code Injection, Command Injection und unsichere Deserialisierung, die rattle mit der Eingabe als Beweis meldet
Ergebnis: Ein Fuzzing-Target für den
IniParserund seine Abstürze, gefunden, minimiert und als Regressionstests festgehalten, bis der Fuzzer 30 Sekunden ohne Fund läuft -
Sitzung 6: Am eigenen Code: kombinieren und voneinander lernen
Welche Technik findet in deinem Code, was deine Tests übersehen?
- Die drei Techniken als Entscheidungslandkarte: Eigenschaften verallgemeinern, Mutation Testing misst die Suite, Fuzzing prüft die Wirklichkeit
- Auswahlfragen: Lässt sich eine Eigenschaft formulieren? Wie gut ist die vorhandene Suite? Kommen Bytes von außen?
- Kombinationen: eine Eigenschaft als Orakel für den Fuzzer, Mutation Testing, das mit festem Seed reproduzierbar prüft, ob Properties Mutanten töten, die die Beispiele überleben lassen, und Gegenbeispiele und Funde des Fuzzers als Regressionstests und Startkorpus
- Kosten und Einbindung je Technik: Laufzeit, Läufe in der CI, Reproduzierbarkeit
- Etwa eine Stunde Arbeit an deiner eigenen Codebasis, mit dem Werkzeug deiner Sprache, und der Trainer unterstützt dich einzeln
- Austausch reihum: Was hast du angewendet und gefunden, was hat nicht funktioniert, und welche Technik hat sich bei welcher Art von Code bewährt?
Ergebnis: Mindestens eine Technik, idealerweise eine Kombination, an deinem eigenen Code angewendet und begründet, die Erfahrungen der anderen und ein Plan, welche Technik du als Nächstes an welchem Stück Code einsetzt und wie sie in deine CI kommt
Für wen
- Alle, die bereits Erfahrung mit automatisierten Tests haben und darüber hinausgehen wollen
- Entwicklerinnen und Entwickler, deren Testsuite grün ist und die trotzdem Fehler in Produktion erleben
- Teams, die Tests von Coding Agents erzeugen lassen und wissen wollen, ob diese Tests etwas prüfen
- Entwicklerinnen und Entwickler, die nicht in PHP arbeiten, denn die Techniken sind sprachunabhängig
Voraussetzungen
- Erfahrung in der Softwareentwicklung mit einer objektorientierten Programmiersprache
- Erfahrung mit automatisierten Tests: Du schreibst Unit Tests und führst sie aus. Die Begriffe dazu vermittelt „Testen verstehen“
- Grundkenntnisse in PHP für die Übungen der ersten fünf Sitzungen
- Für die Übungen eine lokale Umgebung mit PHP 8.5 oder Podman bzw. Docker für den mitgelieferten Container; die Einrichtung beschreibt die Einladung
- Eine eigene Codebasis mit Tests, an der du in der letzten Sitzung arbeiten darfst, gern auch in einer anderen Sprache. Für die letzte Sitzung muss sie sich ausführen lassen, und die Werkzeuge ihrer Sprache installierst du vorab
Das nimmst du mit
- Du formulierst Eigenschaften und findest Grenzfälle, an die du nie gedacht hättest
- Du machst aus einem gefundenen Gegenbeispiel einen Regressionstest
- Du weißt, was Code Coverage aussagt und was nicht, und misst stattdessen, ob deine Tests etwas prüfen
- Du arbeitest überlebende Mutanten ab und erkennst, wann ein Mutant auf eine Designentscheidung zeigt
- Du prüfst die Robustheit deines Codes gegen unerwartete Eingaben systematisch
- Du hast die Techniken an deinem eigenen Code erprobt und kombiniert
Verwandte Schulungen
Diese Schulung ist Teil des Lernpfads Testen mit PHPUnit.
Dein Trainer
Sebastian Bergmann ist der Schöpfer von PHPUnit, dem De-facto-Standard für das Testen von PHP-Code. Seit 2001 hilft er Teams als Berater und Trainer dabei, wartbare und gut getestete Software zu bauen, seit 2009 als Mitgründer von thePHP.cc.