Als Entwicklungsleiter stelle ich mir häufig die Frage: Wie gut testen wir eigentlich wirklich? Eine hohe Code-Coverage ist zwar schön, sagt aber wenig darüber aus, ob unsere Tests tatsächlich alle wichtigen Testfälle abdecken.
Hier kommt Mutation Testing ins Spiel. Die Idee dahinter ist einfach aber genial: Der Code wird automatisch an verschiedenen Stellen „mutiert“ (verändert) und dann werden die Tests ausgeführt. Wenn die Tests diese Änderungen nicht erkennen, haben wir eine Lücke in unserer Testabdeckung gefunden.
Schauen wir uns ein einfaches Beispiel an:
package info.infokom.swez.pitest;
public class Calculator {
public int addNumbers(int a, int b) {
return a + b;
}
}
Der dazugehörige Test könnte so aussehen:
package info.infokom.swez.pitest;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CalculatorTest {
@Test
void testAddNumbers() {
Calculator calculator = new Calculator();
assertEquals(2, calculator.addNumbers(2, 0));
}
}
Auf den ersten Blick sieht der Test gut aus – er prüft, ob 2+0 = 2 ist.
Lassen wir PIT laufen (https://pitest.org/quickstart/), verändert er unseren Code zum Beispiel wie folgt:
- Ändern des + Operators zu –
- Rückgabe von nur einem der Parameter
- Rückgabe von 0
- Vertauschen der Parameter
Unser Test, wie er aktuell aussieht, würde einige dieser Mutationen „überleben“ lassen – das heißt, der Test würde trotz der Änderung durchlaufen. Unsere Testfälle decken also nicht alle möglichen Situationen ab. Natürlich ist das Beispiel gestellt und jeder würde wahrscheinlich sofort sagen, aber warum testet ihr nicht 2+2 = 4?
„Echter“ Code ist aber wahrscheinlich etwas komplizierter und man hat vielleicht nicht jede Randbedingung gesehen und berücksichtigt.
Wichtig ist noch anzumerken, dass „überlebende“ Mutationen nicht bedeuten, dass unser Code schlecht bzw. falsch ist. Es bedeutet primär, dass unsere Testfälle nicht umfänglich genug sind.
Ein klarer Vorteil ist aber, dass wir, wenn wir sinnvolle Testfälle ergänzen, wir die Wahrscheinlichkeit reduzieren, dass wir in Zukunft versehentlich einen Bug einbauen, der den „Contract“ unseres Codes verletzen würde.
Wir haben dieses Tool in einem Trainings-Workshop an unserem wöchentlichen „Slack“-Day vorgestellt und reges Interesse erzeugt. Eine unsere Bibliotheken wurde dann spontan von einem Kollegen mit pitest unter die Lupe genommen und die Erkenntnisse daraus führten dazu, dass an drei Stellen Code geändert wurde, sowie ein paar Tests ergänzt.