Es gibt eine verbreitete stille Übereinkunft, dass Testcode nicht so sorgfältig sein muss wie Produktionscode. «Es ist ja nur ein Test.»
Das Gegenteil trifft zu. Produktionscode hat Tests, die ihn absichern. Testcode hat nichts. Ein Fehler darin fällt entweder gar nicht auf — der Test ist grün, obwohl er nichts prüft — oder er kostet doppelt: Man sucht den Fehler im Produktionscode, und er war im Test.
Dazu kommt: Tests sind die einzige Dokumentation, die nicht veralten kann. Ein Test, der beschreibt, dass ein Rabatt ab dem sechsten Artikel greift, ist entweder aktuell oder rot.
Die Dreiteilung: Arrange, Act, Assert
Ein Test beantwortet immer dieselben drei Fragen: Wie ist die Ausgangslage? Was geschieht? Was muss danach gelten? Wenn diese drei Teile sichtbar getrennt sind, liest sich der Test in Sekunden.
Alles vermischt
[Fact]
public void Test1()
{
var r = new Rabatt();
Assert.Equal(95m, r.Berechne(100m, 3));
Assert.Equal(90m, r.Berechne(100m, 6));
var k = new Kunde("A", true);
Assert.True(r.GiltFuer(k));
}Arrange / Act / Assert
[Fact]
public void Berechne_AbDreiArtikeln_GibtFuenfProzent()
{
// Arrange
var rabatt = new Rabatt();
// Act
var preis = rabatt.Berechne(100m, 3);
// Assert
Assert.Equal(95m, preis);
}Links prüft ein Test drei verschiedene Dinge; schlägt er fehl, sagt der Name «Test1». Rechts weiss man aus dem Namen, was kaputt ist, ohne den Rumpf zu öffnen.
Ein Konzept pro Test — nicht ein `Assert`
Die oft zitierte Regel «nur ein Assert pro Test» ist zu streng. Drei Asserts, die denselben Sachverhalt von drei Seiten prüfen (Betrag, Währung und Rundung eines Ergebnisses), sind ein Konzept. Drei Asserts über drei verschiedene Regeln sind drei Tests.
Der Name ist die Fehlermeldung
Wenn nachts der Build rot wird, sieht man zuerst einen Testnamen. Er sollte allein genügen, um zu wissen, was kaputt ist.
Das verbreitetste Schema ist dreiteilig: Was — unter welchen Umständen — mit welchem Ergebnis.
// Nichtssagend:
public void TestRabatt()
public void Rabatt_Test_2()
public void SollteFunktionieren()
// Aussagekräftig:
public void Berechne_AbSechsArtikeln_GibtZehnProzentRabatt()
public void Berechne_BeiNullArtikeln_WirftArgumentOutOfRange()
public void Belaste_OhneDeckung_LaesstSaldoUnveraendert()Lange Testnamen sind erlaubt
Ein Testmethodenname wird nie aufgerufen — er wird nur gelesen. Die Regel «Namenslänge passt zum Gültigkeitsbereich» spielt hier keine Rolle. Belaste_OhneDeckung_LaesstSaldoUnveraendert ist ein guter Name, auch wenn er lang ist.
Aufbau, der nicht ablenkt
Viele Tests scheitern an der Vorbereitung: Zwanzig Zeilen Objektaufbau, und die eigentliche Prüfung geht darin unter. Die übliche Antwort ist ein Testdaten-Builder mit sinnvollen Vorgaben, bei dem jeder Test nur setzt, worauf es ihm ankommt:
internal sealed class BestellungBuilder
{
private int _positionen = 1;
private bool _bezahlt = true;
public BestellungBuilder Unbezahlt() { _bezahlt = false; return this; }
public BestellungBuilder MitPositionen(int anzahl) { _positionen = anzahl; return this; }
public Bestellung Baue() => new Bestellung(_positionen, _bezahlt, …);
}
// Im Test steht dann nur das Relevante:
var bestellung = new BestellungBuilder().Unbezahlt().Baue();Der Gewinn ist nicht die gesparte Tipparbeit, sondern dass der Test sagt, worauf es ankommt: Diese Bestellung ist unbezahlt, alles andere ist egal. Bei zwanzig Zeilen Aufbau muss der Leser raten, welcher Wert die Rolle spielt.