Hinter dem Begriff „Test FIRST“ vergeben sich eigentlich gleich zwei bedeutungsvolle Prinzipien. Das eine, sagt aus, wann die Tests geschrieben werden sollen, das andere wie.
Auf das wann gehen wir ein anderes Mal ein wenig näher ein. Da steckt nämlich mehr dahinter als nur das stumpfe „Kein Produktivcode vor Testcode“ (Stichwort TDD).
Hinter dem FIRST, versteckt sich noch ein Akronym. Ähnlich wie hinter SOLID.
F wie Fast
Keiner wartet gern. Besonders keiner, der gerade voll „im Flow“ ist. Jeder Test, den du schreibst, muss schnell auszuführen sein. Damit meine ich nicht, schnell zugänglich, sondern die reine Performance.
Wenn du erst 30 Sekunden auf dein Ergebnis warten musst, wirst du den Test wahrscheinlich seltener (oder vielleicht gar nicht) ausführen.
Langsame Tests haben natürlich auch ihre Daseinsberechtigung. Viele meiner Integrationstests sind teilw. sehr langsam. Aber ich bin auch nicht auf die Tests während meiner Arbeit angewiesen. Insgesamt dauert die Ausführung bis zu 15 Minuten.
Die lasse ich aber spätestens vor dem Release durchlaufen. Mindestens aber einmal pro Woche, wenn ich wirklich alle Tests im Projekt anwerfe.
Solche langsamen Tests sollen aber die Ausnahme sein. Die Regel sollten kleine und schnelle Tests sein.
Mit schnell, meine ich nur wenige Millisekunden.
Sagen wir mal, du hast 25.000 Tests. Selbst bei 25ms pro Test, musst du schon mal 10 Minuten warten, bis die Software durchgetestet wurde.
I wie Independent
Während der Entwicklung führst du garantiert nicht alle deine Tests gleichzeitig aus.
Aber was genau bringt es dir, wenn der Test nur erfolgreich ist, wenn vorher andere Tests bestanden wurden? Oder im Gegenteil: der Test schlägt in Kombination mit anderen Tests fehl.
Ein Test muss immer unabhängig von anderen Tests sein.
Der Test muss aber auch unabhängig von dem Benutzer, Computer, Internetverfügbarkeit, Gemütszustand oder auch den Sonnenstand sein.
Sollten deinen Tests auf irgendwelche Testdaten zugreifen, dann pack die Daten von mir aus mit in dein Repository und gut ist (LFS nicht vergessen). Immer noch besser als lokal auf dem Rechner oder irgendwo im Netzwerk.
R wie Repeatable
Was nützt dir eigentlich ein Test, wenn du dich nicht auf ihn verlassen kannst?
Richtig! Nichts!
Ideal ist es, wenn ein Test keine Spuren hinterlässt und sich vorher die Idealbedingungen schafft. Schau mal in deinem Test-Framework, da gibt es garantiert Möglichkeiten, wie man so etwas umsetzen kann.
Bei NUnit (C#), jUnit (Java) und auch PHPUnit sind es setUp und tearDown. Bei Jest (JavaScript/TypeScript) sind es die Funktionen beforeEach und afterEach. Bei MSUnit (C#) ist es wiederum TestInitialize bzw. TestCleanup.
Auch wenn es Menschen gibt, die diese Methoden ein wenig kontrovers betrachten, mag ich die und lasse diese immer in meine Tests einfließen.
Wenn ein Test beispielsweise, in eine Datenbank Einträge geschrieben hat, lösche diese wieder im TearDown.
S wie Self-Validating
Der beste Test bringt dir absolut nichts, wenn du noch einmal manuell das Ergebnis prüfen musst. Dann dauert der Test nämlich wieder sehr lange und ist nicht mehr unabhängig.
Zumal man das Ergebnis auch mal missinterpretieren kann.
Wenn du das Ergebnis nicht kontrollieren kannst, dann brauchst du auch den Test nicht. Überlege dir hierfür eine andere Lösung.
Problematisch sind z.B. binäre Dateien. Beispielsweise, ob ein PDF korrekt erstellt wurde. Eine Möglichkeit hierfür wäre es, ein MD5 Hash über die Datei zu bilden und prüfen, ob die Hashes identisch sind. Leider sagt diese Methode nicht, was genau falsch ist. Man kann damit aber nachhaltig sicherstellen, dass die PDFs immer noch genauso generiert werden, wie gewünscht.
Hält bis zur nächsten Font- oder Logo-Änderung 🙂
Aber auch dafür gibt es meistens schon Lösungen. Schließlich bist du nicht der Erste, der vor diesem Problem steht. Auch eine GUI kann getestet werden.
T wie Timely
Und das ist jetzt der Punkt, den viele unter dem Begriff „Test first“ verstehen.
Allein zu diesem Punkt könnte man ganze Bücher füllen.
Ich versuche aber mal auf das Wesentliche einzugehen.
Schreibe erst ein Test, dann den Code. Im schlimmsten Fall, direkt nachdem du den Code geschrieben hast. Tests im Nachgang hinzuzufügen, ist immer problematisch, weil:
- Du nicht mehr ganz genau weißt, was der Code genau macht und du den Code zunächst wieder verstehen musst (ergänzend hierzu).
- Der Code ggf. nicht testbar ist und du erst aufwendig die eigentliche Funktionalität herausoperieren musst.
- Du einfach dein „Inneren Schweinehund“ überwinden musst, um die Tests zu schreiben
- Es zu diesem Zeitpunkt nicht unbedingt die wirtschaftlichste Entscheidung ist.
Fazit zu Test FIRST
Kommen wir zum Fazit.
Einen guten Test zu schreiben ist nicht sonderlich schwer, wenn man sich an ein paar Regeln hält.
- Schreibe erst den Test, dann den Code
- Der Test muss performant sein
- Der Test muss isoliert ausführbar sein
- Ein Test darf keine Spuren hinterlassen
- Ein Test muss eindeutig über den Erfolg oder Misserfolg berichten. Es gibt kein Interpretationsspielraum.
Und wahrscheinlich das Wichtigste von allem:
Einen guten Test muss verlässlich sein! Du musst deinen Tests vertrauen (lernen) können. Wenn deine Tests sagen, deine Software funktioniert, dann funktioniert sie auch. Jedenfalls in den Aspekten, in denen du die getestet hast.