Mutation Testing | Teil 1

Inspiriert zu diesem Blogpost wurde Jean-Claude von Swen Ruppert, der an der Java User Group München einen Vortrag zum Thema „Mutation Testing“ hielt. Nun erklärt uns Jean-Claude, was Mutation Testing genau ist und gibt Einblick in eine konkrete Mutation Testing Library.

 

Als Entwickler*in gehört es zum Alltag, dass man zu seinem Code auch Tests schreibt. Dabei stellt sich immer wieder die Frage: Wie gut sind meine Unit Tests? Eine Antwort darauf gibt die Code Abdeckung/Coverage (Line, Branch).

Folgendes gilt es jedoch zu beachten: die Code Abdeckung sagt nur aus, dass eine Zeile Code ausgeführt wurde. Sie sagt hingegen nicht aus, was genau getestet wurde. 100% Code Abdeckung kann man auch erreichen, indem man Tests schreibt und die Assert Statements weglässt.

Besonders wertvoll sind die Tests bei einem Refactoring. Die Tests sollen eine gewisse Sicherheit geben, dass sich beim Refactoring keine (neuen) Bugs einschleichen. Und hier stellen sich dann diverse Fragen:
• Wie gut sind die Tests?
• Haben wir genügend (aber nicht zu viele) Tests?
• Haben wir die richtigen Parameter für die Test Methoden gewählt?


Szenario

Wir gehen von folgendem Szenario aus: Wir habe einen „Legacy“ Code mit Tests (100% Code Coverage) und möchten ein Refactoring machen. Und wir fragen uns jetzt: wie schnell merken wir, dass wir im Code etwas kaputt gemacht haben?

Wenn die Tests alles abdecken, dann müsste jede Änderung einen Test brechen. Dies können wir manuell ausprobieren, indem wir im Code eine einzige kleine Anpassung (Mutation) machen und danach die Tests wieder laufen lassen.
• Wenn nach der Mutation (mindestens) ein Test rot wird, sind unsere Tests gut.
• Wenn aber alle Tests grün bleiben, sind die Tests doch nicht so perfekt und wir sollten diese noch erweitern und verfeinern.

Aber was ist eine Mutation? Wir können z.B. eine “Boundary” anpassen, indem wir ein “>” durch ein “>=“ ersetzen. Solche Mutation(en)  kann man manuell oder durch ein Mutation Framework machen.

 

Mutation Framework Basics

Mutation Testing kann uns die Qualität unserer Tests aufzeigen, indem Tests auf Mutationen unseres Codes ausgeführt werden und am Schluss ein Report generiert wird. Voraussetzung sind aber bereits vorhandene Tests. Mutation Testing selbst erstellt keine Unit Tests für uns.

Die Liste möglicher Mutationen ist sehr lang! Hier nur ein paar Beispiele:
Boundary: z.B. “>” durch “>=“ ersetzen
Negate Conditionals: z.B.    “=“ durch “!=“ ersetzen
Return Werte: Return Werte einer Methode durch einen fixen Wert ersetzen
Mathematische Operationen: z.B. “-” durch “+”ersetzten

Aber was macht ein Mutation Framework nun genau?
• Ein Mutation Framework nimmt das Original Programm P und macht daraus x Mutanten P1 bis Px. Jeder Mutant Px unterscheidet sich vom Original Programm P nur durch eine einzige Mutation.
• Das Framework lässt nun alle Tests laufen gegen P (für die Code Coverage) und jeden Mutanten Px.
• Am Schluss wird ein Report erstellt, der für den gesamten getesteten Code alle Mutanten und deren Status zeigt:

  • Wenn ein Mutant einen Test bricht, hat er den Status “KILLED”. Das ist unser gewünschtes Szenario.
  • Wenn ein Mutant keine Tests bricht, hat er den Status “SURVIVED”. In diesem Fall müssten wir unsere Tests noch verbessern.

 

Mutation Score

Aus dem Status der einzelnen Mutanten kann man den Mutation Score berechnen. Ziel ist es, einen möglichst hohen Mutation Score zu bekommen. Dabei ist abzuwägen, wie viel Aufwand man dafür betreiben möchte.

mutation_score = (Killed Mutants / Total number of Mutants) * 100

 

Beispiel mit PIT

Machen wir doch ein konkretes Beispiel mit dem Framework von pitest.org, auch PIT genannt. (Mehr Information zu PIT folgt im nächsten Blog Post.)

Wir haben einen CalculatorService, der den Preis für ein Produkt berechnet. Wenn das Input total grösser als 200 ist, gibt es 50 Rabatt.

Und dazu noch zwei JUnit 5 Tests. Einen Tests für ein Produkt das teurer ist als 200 und einen Test für ein günstigeres Produkt.

Und wenn man die Tests in der IDE seines Vertrauens laufen lässt, bekommt man eine Code Coverage von 100%. Aber wie viel sind diese 100% wert? PIT wird es uns zeigen.

 

 

PIT selbst läuft hier als Maven Plugin und funktioniert gut mit unseren in JUnit 5 geschriebenen Tests. Wir starten PIT und schauen uns den Report an.

 

 

• Alle Code Zeilen sind grün, d.h. Code Coverage ist 100%.
• Wir bekommen einen Mutation Score von 80.
• Es wurden 5 Mutanten erstellt. 4 davon haben Status “KILLED” und sind grün eingefärbt.
• Ein Mutant hat Status “SURVIVED”, d.h. es gibt eine Mutation im Code, die keiner unserer Tests bemerkt hat. Es ist eine Mutation vom Typ CONDITIONALS_BOUNDARY. Konkret wurde hier das “>” im Code durch ein “>=“ ersetzt. Dass dies von keinem Test bemerkt wurde ist ein Hinweis darauf, dass die Parameter für unseren Test nicht optimal gewählt wurden.


Wir ändern im “no_discount” Test den Test Wert von 199 auf 200 und lassen PIT nochmals laufen. Mit diesen Test Parametern bekommen wir einen Mutation Score von 100.

 

 

Fazit

Wir haben gelernt, dass die Code Coverage nicht aufzeigt, welcher Code getestet wurde. Vielmehr zeigt sie nur, welcher Code nicht getestet wurde. Hier kann Mutation Testing weiterhelfen.

Das Beispiel hat gezeigt, dass die Parameter-Werte nicht optimal gewählt wurden und man trotz 100% Code Coverage die Tests verbessern kann.

Übrigens: Mutation Testing kann relativ einfach in ein Projekt mit bestehenden JUnit 5 Tests eingebunden werden. Allerdings klappt Mutation Testing nicht gratis. Die Ausführungszeit der Tests wird länger, da einerseits die Mutanten erstellt und andererseits massiv mehr Tests ausgeführt werden müssen. In unserem Beispiel werden die Tests nun sechs mal (1x Code Coverage und 5x Mutanten) ausgeführt. Aber in PIT besteht die Möglichkeit zu konfigurieren, für welchen Code (Packages) Mutationen erstellt werden sollen.

Möchtest du mehr zu PIT erfahren?

Der nächste Blog Post steht schon in den Startlöchern. Dort gehen wir dann näher auf PIT ein und werden sehen, wie man PIT in seine eigenen JUnit Tests einbinden und damit mit eigenen Experimenten starten kann.


Weiterführende Links:

Start hunting the bugs – https://www.meetup.com/Lightweight-Java-User-Group-Munchen/events/284504944/

PIT – https://pitest.org

 

 

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.