TDD, Asimov und das Nullgesetz
Einführung
In diesem Beitrag werden wir eine kurze Einführung in die testgetriebene Entwicklung (TDD) sehen. Wir wollen einige Dinge erkunden, die wir auf dieser aufregenden Reise gelernt haben und hoffen, dass Sie Spaß daran haben werden. Wer weiß? Vielleicht schließen Sie sich diesem dunklen Kult an.
Was ist testgetriebene Entwicklung?
TDD oder Test Driven Development ist eine Programmiertechnik, die darin besteht, zuerst einen Test zu schreiben und dann den „Produktionscode“. Dies wird erreicht, indem drei Schritte immer und immer wieder wiederholt werden:
- Schreiben Sie einen Test, der für den Code, den wir entwickeln wollen, fehlschlägt
- Schreiben Sie den minimalen Code, der den Test bestehen lässt (machen Sie ihn grün).
- Refaktorieren Sie den Code, damit der Test grün bleibt.
Nach einer langen Zeit der Übung beschreibt Onkel Bob drei einfache Regeln für das Schreiben von Code (bitte lesen Sie den verlinkten Artikel; er ist sehr interessant, versprochen).
Die drei Regeln sind:
- Du kannst keinen produktiven Code schreiben, es sei denn, es geht um einen Test, der grünes Licht gibt.
- Du kannst nicht mehr Code schreiben, als nötig ist, damit ein Unit-Test fehlschlägt; und ein Kompilierfehler zählt als Fehler.
- Du kannst nicht mehr Code schreiben, als nötig ist, damit ein fehlgeschlagener Test grünes Licht bekommt.
Sowohl in den drei Schritten von TDD als auch in Onkel Bobs Regeln gibt es eine Menge interessanter Dinge. Schauen wir uns einige von ihnen an.
TDD-Zyklus
Ja, richtig, zuerst testen, aber was soll ich testen?
Das ist eine typische Frage, wenn man ein neues Leben beginnen will. Die Antwort auf diese Frage ist sehr einfach: Testen Sie den Code, den Sie hinzufügen wollen. Lachen Sie bitte nicht, sondern versuchen Sie es.
Der TDD-Zyklus mag einfach erscheinen, aber der erste Test eines Projekts ist immer der schwierigste. Es spielt keine Rolle, ob wir an einem neuen Projekt arbeiten oder ob wir in ein Projekt hineingefallen sind, das es schon eine Weile gibt. Es ist am schwierigsten, weil wir alles erstellen müssen, was wir zum Schreiben brauchen, d.h. das Testprojekt erstellen, in dem es „leben“ wird, Verzeichnisse, Infrastruktur und so weiter. Aber wenn man ihn einmal hat, ist es wirklich einfach, einen weiteren Test hinzuzufügen.
Erinnern Sie sich an das Zitat von Onkel Bob: „Ein Kompilierungsfehler zählt als Fehler“. Kein Grund zur Panik. Sie sind in der Schleife. Beenden Sie das Scaffolding und gehen Sie zum nächsten Schritt über, der Ihr erster Test sein sollte.
Schreiben Sie den Test, der eine Variable einer Klasse instanziiert, die noch nicht existiert, d.h. einen Test, der fehlschlägt. Erstellen Sie nun die Klasse. Geben Sie ihr einen Namen; der Test ist grün, Ihr System ‚funktioniert‘. Rufen Sie nun die Methode auf, in die Sie die Logik einfügen wollen; sie existiert noch nicht, perfekt! Das System schlägt fehl, aber vor ein paar Sekunden hat es noch funktioniert. Verwenden Sie die Werkzeuge, die Ihnen Ihre IDE zur Verfügung stellt, um die Methode zu erstellen, die nichts tut… Toll, sie ist grün, das System funktioniert wieder.
Auch hier gilt: Keine Angst, Sie haben Entscheidungen getroffen und bekommen sofort eine Rückmeldung: Passt der Klassenname? Was ist mit dem Methodennamen? Den Parametern? Den Rückgabetypen? Ja? Toll! Nein? Besser! Sie haben die Rückmeldung, dass etwas nicht stimmt, Sie müssen vielleicht einen Namen überdenken…
Na also, geht doch! Warte mal, warte mal…
Sie haben noch keine Version des Systems gebaut, es noch nicht in Produktion gegeben und noch keinen Benutzer damit arbeiten lassen, und Sie haben schon gesehen, dass etwas nicht stimmt. Sie haben während der Entwicklungszeit sofortiges Feedback, dass etwas nicht stimmt. Halten Sie das immer noch für Zeitverschwendung? Mit dem, was Sie gelernt haben, können Sie zurückgehen und etwas anderes versuchen, aber dieses Mal haben Sie mehr Informationen. Gehen Sie zurück und fangen Sie noch einmal an. Mit nur zwei oder drei Codezeilen haben Sie etwas gelernt, und jetzt ist es wahrscheinlicher, dass Sie beim nächsten Versuch erfolgreich sind.
Machen Sie den Test erfolgreich
An diesem Punkt haben Sie einen Test, der fehlschlägt. Normalerweise sieht der Testcode etwa so aus:
- Eine Instanz der Klasse, die Sie testen wollen (Arrange).
- Ein Methodenaufruf (Act).
- Ein Methodenaufruf (Act).
- Ein Methodenaufruf (Act).
- Überprüfen Sie, ob die Logik das tut, was sie soll (Assert).
Jetzt ist es an der Zeit, den Code zu schreiben, der den Test erfolgreich macht. Nur der minimale Code, der den Test erfolgreich macht. Ein Beispiel könnte ein einfaches „gibt true zurück“ sein. Keine Angst, wir haben den Zyklus noch nicht beendet, wir haben den Refactoring-Schritt noch nicht gemacht. Und da TDD ein iterativer Prozess ist, wird der nächste Test, den Sie schreiben, Sie dazu veranlassen, den „Produktionscode“ zu ändern, also haben Sie keine Angst, einen „Vertrauensvorschuss“ zu geben: Wie Sie bereits in Onkel Bobs Artikel gelesen haben, hat der Code vor ein paar Minuten noch fehlerfrei funktioniert. Dies nennt man „kleine Schritte“; wir werden später darüber sprechen.
Versuchen Sie Assert-Act-Arrange anstelle von Arrange-Act-Assert
Wir haben das typische Muster für das Schreiben guter Beweise gesehen: Anordnen-Handeln-Bestätigen. Wie wäre es, wenn wir es andersherum machen?
Assert
Überlegen Sie sich, wie Sie den Code, den Sie testen wollen, überprüfen wollen. Überprüfen Sie einen Wert in einer Eigenschaft? Den Wert, der von einer Methode zurückgegeben wird? Überlegen Sie es sich und schreiben Sie die Assert zuerst. Du triffst Entscheidungen und holst dir Feedback, ob es sich lohnt, es auf diese Weise zu tun.
Aktion
Führe die Aktion aus, rufe die Methode auf, damit sie den Code ausführt und ihr Ding macht. Du hast eine Möglichkeit zu überprüfen, ob sie es richtig macht. Hier entscheidest du, wie du die Methode aufrufst, mit welchen Parametern, welchem Namen usw.
Bereich
Instanziere dein Objekt mit den notwendigen Abhängigkeiten.
In jedem „Abschnitt“ des Tests entwickeln, entwerfen und experimentieren Sie mit dem Code, den Sie in Ihrem Kopf haben. Er ist noch nicht geschrieben, aber Sie haben bereits mehrere Mechanismen, um zu überprüfen, ob das, was Sie im Kopf haben, in das System passt. Und wenn Ihnen etwas seltsam vorkommt oder Sie sehen, dass Sie etwas übersehen haben, hurra! Gehen Sie einen Schritt zurück und versuchen Sie es noch einmal. Du bist wie Doktor Seltsam mit dem Auge des Agamotto.
Bei TDD geht es nicht um Geschwindigkeit, sondern um die Häufigkeit: im Feedback, beim Ausführen des Codes, beim Überprüfen, ob etwas kaputt gegangen ist…
Refaktorieren Sie den Code, den Sie hinzugefügt haben
Nun, wir haben den Test von rot (Fehler) auf grün (bestanden) gebracht. Jetzt ist es an der Zeit zu prüfen, ob der Code, den wir geschrieben haben, leicht zu lesen ist, Duplikate zu entfernen, nach schlechten Gerüchen zu suchen… Dieser Schritt ist in der Regel der schwierigste für mich, weil wir diese Änderungen unter Berücksichtigung von zwei Dingen vornehmen müssen:
- Nicht den Test, den wir geschrieben haben, oder einen anderen kaputt machen.
- Nicht mehr Logik als nötig implementieren.
Die Funktionalität, die wir entwickeln wollen, ist noch nicht fertig, also gehen wir zurück zum ersten Schritt und erstellen einen weiteren Test. Und so weiter, bis wir die Funktionalität fertiggestellt haben.
Einfach, nicht wahr? Spoiler: Nein, ist es nicht. Leichter gesagt als getan.
Was ich gelernt habe
Diese Schritte sehen einfach aus, aber dahinter verbirgt sich eine Menge Wissen. Ich möchte Ihnen zeigen, was ich aus diesen Gesetzen, aus dem Zyklus und aus der immer wiederkehrenden Praxis gelernt habe.
Es gibt keine Silberkugeln, aber es gibt Leuchtspurgeschosse
TDD ist keine Silberkugel, es erinnert mich eher an eine Tracer Bullet, wie sie in ‚The Pragmatic Programmer‘ [2] beschrieben wird.
Es ist eine Möglichkeit, die berühmten kleinen Schritte zu machen und Feedback zu bekommen, ob wir in die richtige Richtung gehen. Wenn wir feststellen, dass dies nicht der richtige Weg ist, gehen wir einen Schritt zurück und gehen einen neuen in eine andere Richtung. Wie ein Mandalorianer sagen würde: „Das ist der Weg“.
Nein, wirklich, was soll ich testen?
Wenn Sie sich nicht sicher sind, ob Sie den Code, den Sie hinzufügen wollen, testen sollen? Das ist nicht der richtige Weg.
Ein guter Ausgangspunkt kann sein, wenn wir einen Fehler beheben. Sobald wir den Grund für den Fehler gefunden haben, in der Regel nach einiger Fehlersuche, beheben Sie ihn nicht sofort. Versuchen Sie zunächst, einen Test zu erstellen, der den Fehler reproduziert, und konzentrieren Sie sich darauf, wie die Klasse instanziiert wird, welche Werte die Eigenschaften haben, welche Methode aufgerufen wird… Es kommt oft vor, dass es kompliziert ist, die Klasse zu instanziieren, es gibt einige Abhängigkeiten, die uns stören. „Willkommen in meiner Welt, Neo.
Notieren Sie sich den Test, den Sie erstellen wollen. Versuchen Sie nun, die Abhängigkeit zu entkoppeln und zaubern Sie, extrahieren Sie die Abhängigkeit zu einer Methode, dann zu einer Klasse, usw. Seien Sie kreativ. Fügen Sie währenddessen eine weitere Notiz hinzu, um den Code zu testen, den Sie gerade umstrukturiert haben. Das Wichtigste ist, dass Sie nicht den Fokus auf das verlieren, was Sie gerade tun.
Karate Kid und Nobelpreise
.
Erinnern Sie sich an den Film „Karate Kid“? Erinnern Sie sich daran, was Mr. Miyagi zu DanielSan sagte: „Wax on, wax off“. Es klang einfach, aber für DanielSan machte diese Übung keinen Sinn, um Karate zu lernen. Es ist ähnlich wie bei den ersten Malen TDD: Wenn wir in der Praxis beharren, werden wir früher oder später die Wahrheit dahinter erkennen.
Kennen Sie das Zitat von Kent Beck: „Erst soll es funktionieren, dann soll es richtig sein“ [3]?
In der Softwareentwicklung wissen wir, dass die erste Version des Codes, den wir zur Lösung eines Problems schreiben, oft nicht die beste Lösung ist. Normalerweise führen wir Iterationen durch, und in jeder Iteration ändern wir den Code, bis wir das gewünschte Verhalten erreichen.
Daniel Kahneman [4], ein mit dem Nobelpreis ausgezeichneter Psychologe und Wirtschaftswissenschaftler, spricht in seinem Buch „Schnelles Denken, langsames Denken“ darüber, wie das menschliche Gehirn funktioniert. Das menschliche Gehirn hat zwei Modi: langsam und schnell. Der schnelle Modus ist darauf ausgelegt, schnelle Antworten auf Fragen und Situationen zu geben; der langsame Modus ist der analytische Modus.
Der schnelle Modus ist das Ergebnis der Evolution, um den Ressourcenverbrauch bei Entscheidungen zu reduzieren, die es dem Einzelnen ermöglichen, in bestimmten Situationen zu überleben. Wenn wir zum Beispiel ein Geräusch in einem Gebüsch hinter uns hören, haben wir das Bedürfnis, uns vor der Gefahr zu verstecken. Heutzutage wissen wir bereits, dass es sehr selten ist, dass uns ein Löwe auflauert, um uns zu fressen. Dies ist der langsame Modus des Gehirns in Aktion.
Es gibt eine Verbindung zwischen den Worten von Kent Beck und Daniel Kahneman. Während wir programmieren, versuchen wir zuerst, es zum Funktionieren zu bringen, und dann machen wir es richtig, indem wir refaktorisieren, Verantwortlichkeiten trennen, Abstraktionen schaffen und so weiter.
TDD hilft uns, diesen schnellen Modus unseres Gehirns zu stoppen und den langsamen Modus zu wecken, damit wir von Anfang an arbeiten können. Indem wir zuerst den Test schreiben, übernimmt der langsame Modus die Kontrolle. Wir fangen an, über Klassennamen, Methoden und Parameter nachzudenken und darüber, wie wir überprüfen wollen, ob der Code das tut, was wir wollen, und so weiter. Das Ergebnis ist, dass der Code nicht nur das tut, was wir wollen, sondern dass er auch für Menschen lesbar ist.
Neues Projekt, Legacy und Zeitverschwendung
Wenn wir ein neues Projekt beginnen, sind wir in der Lage, sehr schnell Funktionalität zu liefern. Der Schnellmodus des Gehirns ist aktiv, wir denken, dass wir eine gute Grundlage schaffen, weil wir nicht die gleichen Fehler machen werden wie beim vorherigen Projekt. Es ist ein neues Projekt, wir haben keine Zeit zum Testen, wir müssen die Funktionen so schnell wie möglich abschließen, wir haben eine gute Geschwindigkeit… Plötzlich, eines Tages, sinkt die Geschwindigkeit, wir können nicht mehr so viele Funktionen abschließen wie früher, und das neue Projekt wird zu einem Legacy-Projekt. Das Team beginnt zu sagen, dass wir aufhören und neu anfangen sollten. Ala, ein Déjà vu. Was haben wir dieses Mal falsch gemacht?
„Erst machen wir es gut, dann machen wir es richtig“ (wax on, wax off).
Der schnelle Modus des Gehirns ist gut darin, Dinge schnell zum Laufen zu bringen, aber haben wir den langsamen Modus „es richtig machen lassen“?
Hier machen die Gesetze von TDD Sinn. Sie ermöglichen es dem langsamen Modus zu arbeiten. In dem Buch „TDD by example“ empfiehlt Kent Beck, eine Liste der Tests zu erstellen, die wir durchführen wollen, bevor wir beginnen. Ich habe die Vorteile dieser Vorgehensweise erst nach einer Weile verstanden. Dieser Schritt dient zwei Zwecken:
- Den langsamen Modus des Gehirns aufzuwecken und ein Feedback zu den ersten Gedanken und Entscheidungen über die Lösung, die wir entwerfen, zu erhalten.
- Fokussieren. Wir beginnen eine Liste von Aufgaben, die uns auf einen Test nach dem anderen konzentrieren. Ich werde diesen Punkt im nächsten Abschnitt erläutern.
Fokus. Das Nullgesetz
Bei einem neuen Projekt ist es einfach, die gesamte „Logik“ im Kopf zu haben und die Funktionalität schnell zu entwickeln. Die Komplexität des Systems ist anfangs noch überschaubar. Aber mit der Zeit nimmt die Komplexität exponentiell zu.
Eine Liste der Tests, die man hinzufügen möchte, auf einem „Stück Papier“ zu haben, erlaubt es uns, uns nur auf den aktuellen Test zu konzentrieren, da wir uns keine Gedanken über den Rest des Systems machen müssen. Es ist bereits getestet, und vor einer Minute hat noch alles funktioniert [1]! Wenn man etwas kaputt macht, liegt das an dem Code, den man gerade hinzugefügt hat.
Wenn wir auf diese Weise testen, erhalten wir ein Projekt, das immer wie neu aussieht. Alles hat gerade noch funktioniert, so dass die Angst vor dem Einsatz an einem Freitagnachmittag „verschwindet“. Wow, das ist wirklich schnell eskaliert, Juanma. Nun, OK, Sie haben das Selbstvertrauen, an einem Freitagnachmittag zu deployen, aber tun Sie es NICHT.
Ich nenne dies das TDD-Gesetz Null, wie das von Asimov [5]. Bevor Sie den Testcode schreiben, schreiben Sie den Test auf ein Blatt Papier.
Ist das schneller?
Ich habe keine Zweifel, aber ich habe auch keine Beweise. Ich habe nicht gezählt, wie oft ich NICHT bis 3 Uhr morgens aufgeblieben bin, um einen Fehler in der Produktion zu beheben.
Was ist Ihrer Meinung nach schneller? Das Hinzufügen von Funktionen zu:
- Ein neues Projekt mit bereits Unit-getesteten Features als Ergebnis der Anwendung von TDD
- Ein Projekt, das keine Tests hat, um zu prüfen, ob man etwas kaputt gemacht hat.
- Ein Projekt, das keine Tests hat, um zu prüfen, ob man etwas kaputt gemacht hat.
Es ist interessant, darüber nachzudenken. Ja, aber wir haben es nicht geschafft, uns ist die Zeit ausgegangen. Zeit, ein so kostbares Gut. Deshalb testen wir unseren Code. Es ist ein weit verbreiteter Fehler zu sagen: „Wir testen nicht, weil wir keine Zeit haben“. Wenn wir mit dem Testen beginnen, stellen wir bald fest, dass genau das Gegenteil der Fall ist: „Wir testen, weil wir KEINE Zeit haben“.
Vielleicht hilft uns dieses Bild, es zu verstehen:
Zusammenfassung
Ich hoffe, Sie finden diesen Beitrag nützlich und unterhaltsam. Es ist eigentlich egal, ob Sie TDD praktizieren oder nicht: wichtig ist, dass Sie Ihren Code testen lassen. Und ja, es spielt keine Rolle, ob diese Tests gut oder schlecht sind. Ich habe lieber einen schlechten Test, der bei jedem Integrationszyklus (IC) läuft, als einen perfekten Test, der weder geschrieben noch ausgeführt wird.
Das ist der erste Schritt. Nach einer Weile wird man ganz natürlich dazu übergehen, TDD zu praktizieren; es ist nur eine Frage der Zeit.
Bibliographie
- http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd
- https://pragprog.com/titles/tpp20/the-pragmatic-programmer-20th-anniversary-edition/
- https://es.wikipedia.org/wiki/Kent_Beck
- https://en.wikipedia.org/wiki/Daniel_Kahneman
- https://en.wikipedia.org/wiki/Three_Laws_of_Robotics