Dieser Artikel ist eine Adaption eines Kapitels aus dem Buch The Complete Software Career Guide. Sein Autor, John Sonmez, schreibt es und veröffentlicht einige Kapitel auf seiner Website.
Was ist TDD und Unit-Testing [Übersetzung] - 1

Ein kurzes Glossar für Einsteiger

Unter Unit-Testing oder Unit-Testing versteht man einen Prozess in der Programmierung, der es ermöglicht, einzelne Module des Quellcodes eines Programms auf Korrektheit zu überprüfen. Die Idee besteht darin, Tests für jede nicht triviale Funktion oder Methode zu schreiben. Regressionstests sind eine allgemeine Bezeichnung für alle Arten von Softwaretests, die darauf abzielen, Fehler in bereits getesteten Bereichen des Quellcodes zu erkennen. Solche Fehler – wenn nach Änderungen am Programm etwas, das weiterhin funktionieren sollte, nicht mehr funktioniert – werden als Regressionsfehler bezeichnet. Rotes Ergebnis, fehlgeschlagen – Fehlschlagen des Tests. Der Unterschied zwischen dem erwarteten und dem tatsächlichen Ergebnis. Grünes Ergebnis, bestanden – positives Testergebnis. Das tatsächliche Ergebnis unterscheidet sich nicht von dem, was erzielt wurde. ***
Was ist TDD und Unit-Testing [Übersetzung] - 2
Ich habe eine sehr gemischte Beziehung zu Test Driven Development (TDD) und Unit-Tests, die von Liebe zu Hass und wieder zurück reicht. Ich war ein begeisterter Fan und gleichzeitig ein misstrauischer Skeptiker gegenüber der Verwendung dieser und anderer „Best Practices“. Der Grund für meine Haltung liegt darin, dass in Softwareentwicklungsprozessen ein ernstes Problem aufgetreten ist: Entwickler und manchmal auch Manager verwenden bestimmte Tools und Methoden nur, weil sie zu „Best Practices“ gehören. Der wahre Grund für ihren Einsatz bleibt unklar. Eines Tages begann ich mit der Arbeit an einem bestimmten Projekt und dabei wurde mir mitgeteilt, dass wir Code ändern würden, der einer Vielzahl von Unit-Tests unterzogen wurde. Kein Scherz, es gab ungefähr 3000 davon. Das ist normalerweise ein gutes Zeichen, ein Signal dafür, dass die Entwickler fortschrittliche Methoden verwenden. Der Code bei diesem Ansatz ist meist strukturiert und basiert auf einer gut durchdachten Architektur. Kurz gesagt, das Vorhandensein von Tests hat mich glücklich gemacht, schon allein deshalb, weil es mir die Arbeit als Mentor von Programmierern erleichtert hat. Da wir bereits Unit-Tests hatten, musste ich nur noch das Entwicklungsteam vernetzen, um es zu unterstützen und mit dem Schreiben unseres eigenen Codes zu beginnen. Ich habe die IDE (Integrated Development Environment) geöffnet und das Projekt geladen.
Was ist TDD und Unit-Testing [Übersetzung] - 3
Es war ein großes Projekt! Ich habe einen Ordner mit der Bezeichnung „Unit Tests“ gefunden. „Großartig“, dachte ich. - Lass es uns starten und sehen, was passiert. Es dauerte nur ein paar Minuten und zu meiner Überraschung waren alle Tests erfolgreich, alles war grün ( „grün“ ist ein positives Ergebnis des Tests. Signalisiert, dass der Code wie erwartet funktioniert. Rot bedeutet also „Fehler“ oder „fehlgeschlagen“. Es gibt einen Fall, in dem der Code nicht richtig funktioniert (Anmerkung des Übersetzers ). Sie alle haben die Prüfung bestanden. In diesem Moment erwachte der Skeptiker in mir. Wie kommt es, dass dreitausend Unit-Tests alle auf einmal durchgeführt wurden – und ein positives Ergebnis lieferten? In meiner langen Praxis konnte ich mich nicht daran erinnern, jemals mit der Arbeit an einem Projekt begonnen zu haben, ohne einen einzigen negativen Unit-Test im Code. Was zu tun? Manuell prüfen! ChY wählte einen zufälligen Test, nicht den aufschlussreichsten, aber es war sofort klar, was er überprüfte. Doch beim Durcharbeiten ist mir etwas Absurdes aufgefallen: Der Test enthielt keine Vergleiche mit dem erwarteten Ergebnis (Behauptungen)! Das heißt, in Wirklichkeit wurde überhaupt nichts überprüft ! Es gab bestimmte Schritte im Test, sie wurden durchgeführt, aber am Ende des Tests, wo er die tatsächlichen und erwarteten Ergebnisse vergleichen sollte, gab es keine Kontrolle. Der „Test“ hat nichts getestet. Ich habe einen weiteren Test geöffnet. Noch besser: Der Vergleichsoperator mit dem Ergebnis wurde auskommentiert. Brillant! Dies ist eine großartige Möglichkeit, einen Testdurchlauf durchzuführen. Kommentieren Sie einfach den Code aus, der zum Scheitern führt. Ich habe einen weiteren Test überprüft, dann noch einen ... Keiner von ihnen hat etwas überprüft. Dreitausend Tests, und alle sind völlig nutzlos. Es gibt einen großen Unterschied zwischen dem Schreiben von Unit-Tests und dem Verständnis von Unit-Tests und testgetriebener Entwicklung (TDD).

Was ist Unit-Test?

Was ist TDD und Unit-Testing [Übersetzung] - 4
Die Grundidee des Unit-Tests besteht darin, Tests zu schreiben, die die kleinste „Einheit“ des Codes testen. Unit-Tests werden normalerweise in derselben Programmiersprache geschrieben wie der Quellcode der Anwendung. Sie werden direkt erstellt, um diesen Code zu testen. Das heißt, Unit-Tests sind Code, der die Richtigkeit anderen Codes überprüft. Ich verwende das Wort „Test“ in diesem Zusammenhang recht großzügig, da Unit-Tests in gewissem Sinne keine Tests sind. Sie erleben nichts. Was ich meine ist, dass man bei der Durchführung eines Komponententests normalerweise nicht feststellt, dass ein Code nicht funktioniert. Dies entdecken Sie beim Schreiben des Tests, da Sie den Code so lange ändern, bis der Test grün wird. Ja, der Code kann sich später ändern und Ihr Test kann dann fehlschlagen. In diesem Sinne ist ein Unit-Test ein Regressionstest. Ein Unit-Test ist nicht wie ein normaler Test, bei dem Sie ein paar Schritte befolgen und sehen, ob die Software ordnungsgemäß funktioniert oder nicht. Beim Schreiben eines Komponententests stellen Sie fest, ob der Code das tut, was er tun soll oder nicht, und ändern den Code, bis der Test erfolgreich ist.
Was ist TDD und Unit-Testing [Übersetzung] - 5
Warum nicht einen Unit-Test schreiben und prüfen, ob er erfolgreich ist? Wenn Sie es so betrachten, werden Unit-Tests zu einer Art absoluter Anforderung für bestimmte Codemodule auf einer sehr niedrigen Ebene. Sie können sich einen Unit-Test als eine absolute Spezifikation vorstellen . Ein Komponententest stellt fest, dass es unter diesen Bedingungen und mit diesem bestimmten Satz von Eingaben eine Ausgabe gibt, die Sie von dieser Codeeinheit erhalten sollten. Echte Unit-Tests identifizieren die kleinste zusammenhängende Codeeinheit, die in den meisten Programmiersprachen – zumindest objektorientierten – eine Klasse ist.

Was wird manchmal als Unit-Test bezeichnet?

Was ist TDD und Unit-Testing [Übersetzung] - 6
Unit-Tests werden oft mit Integrationstests verwechselt. Einige „Unit-Tests“ testen mehr als eine Klasse oder testen große Codeeinheiten. Viele Entwickler behaupten, dass sie Unit-Tests schreiben, obwohl sie in Wirklichkeit Low-Level-Whitebox-Tests schreiben. Streite nicht mit diesen Leuten. Wissen Sie nur, dass sie tatsächlich Integrationstests schreiben und echte Unit-Tests die kleinste Codeeinheit isoliert von anderen Teilen testen. Eine andere Sache, die oft als Unit-Test bezeichnet wird, sind Unit-Tests ohne Prüfung anhand eines erwarteten Werts. Mit anderen Worten: Unit-Tests, die eigentlich gar nichts testen. Jeder Test, ob vereinheitlicht oder nicht, muss eine Art Verifizierung beinhalten – wir nennen es die Überprüfung des tatsächlichen Ergebnisses mit dem erwarteten Ergebnis. Es ist dieser Abgleich, der darüber entscheidet, ob der Test bestanden wird oder nicht. Ein Test, der immer besteht, ist nutzlos. Ein Test, der immer fehlschlägt, ist nutzlos.

Der Wert von Unit-Tests

Warum bin ich ein Unit-Testing-Enthusiast? Warum ist es schädlich, generalisiertes Testen, bei dem nicht der kleinste vom anderen Code isolierte Block, sondern ein größerer Teil des Codes getestet wird, als „Unit-Test“ zu bezeichnen? Was ist das Problem, wenn einige meiner Tests die erhaltenen und erwarteten Ergebnisse nicht vergleichen? Zumindest führen sie den Code aus. Ich werde versuchen, es zu erklären.
Was ist TDD und Unit-Testing [Übersetzung] - 7
Es gibt zwei Hauptgründe für die Durchführung von Unit-Tests. Die erste besteht darin, das Codedesign zu verbessern. Erinnern Sie sich, wie ich sagte, dass Unit-Tests nicht wirklich ein Test sind? Wenn Sie richtige Komponententests schreiben, zwingen Sie sich dazu, die kleinste Codeeinheit zu isolieren. Diese Versuche werden dazu führen, dass Sie Probleme in der Struktur des Codes selbst entdecken. Möglicherweise fällt es Ihnen sehr schwer, die Testklasse zu isolieren und ihre Abhängigkeiten nicht einzubeziehen, und dadurch erkennen Sie möglicherweise, dass Ihr Code zu eng gekoppelt ist. Möglicherweise stellen Sie fest, dass sich die Kernfunktionalität, die Sie testen möchten, über mehrere Module erstreckt, was Sie zu der Annahme verleitet, dass Ihr Code nicht kohärent genug ist. Wenn Sie sich hinsetzen, um einen Komponententest zu schreiben, stellen Sie möglicherweise plötzlich fest (und glauben Sie mir, das passiert!), dass Sie keine Ahnung haben, was der Code tun soll. Dementsprechend können Sie keinen Unit-Test dafür schreiben. Und natürlich kann es sein, dass Sie einen echten Fehler in der Implementierung des Codes finden, da der Unit-Test Sie dazu zwingt, über den Tellerrand hinauszuschauen und verschiedene Eingabesätze zu testen, die Sie möglicherweise nicht berücksichtigt haben.
Was ist TDD und Unit-Testing [Übersetzung] - 8
Wenn Sie sich beim Erstellen von Komponententests strikt an die Regel „Testen Sie die kleinste Codeeinheit isoliert von den anderen“ halten, werden Sie mit diesem Code und dem Design dieser Module zwangsläufig auf alle möglichen Probleme stoßen. Im Softwareentwicklungslebenszyklus ist Unit-Test eher eine Evaluierungsaktivität als eine Testaktivität. Das zweite Hauptziel von Unit-Tests besteht darin, einen automatisierten Satz von Regressionstests zu erstellen, die als Spezifikation des Softwareverhaltens auf niedriger Ebene dienen können. Was bedeutet das? Wenn Sie den Teig kneten, zerbricht er nicht. Aus dieser Sicht handelt es sich bei Unit-Tests um Tests, genauer gesagt um Regressionstests. Der Zweck von Unit-Tests besteht jedoch nicht einfach darin, Regressionstests zu erstellen. In der Praxis kommt es bei Unit-Tests selten zu Regressionen, da eine Änderung an der zu testenden Codeeinheit fast immer Änderungen am Unit-Test selbst beinhaltet. Regressionstests sind auf einer höheren Ebene viel effektiver, wenn der Code als „Black Box“ getestet wird, da auf dieser Ebene die interne Struktur des Codes geändert werden kann, während erwartet wird, dass das äußere Verhalten gleich bleibt. Unit-Tests wiederum überprüfen die interne Struktur, sodass die Unit-Tests nicht fehlschlagen, wenn sich diese Struktur ändert. Sie werden unbrauchbar und müssen nun geändert, weggeworfen oder neu beschrieben werden. Sie wissen jetzt mehr über den wahren Zweck von Unit-Tests als viele erfahrene Softwareentwickler.

Was ist testgetriebene Entwicklung (TDD)?

Was ist TDD und Unit-Testing [Übersetzung] - 9
Im Softwareentwicklungsprozess ist eine gute Spezifikation Gold wert. Der TDD-Ansatz besteht darin, dass Sie vor dem Schreiben von Code zunächst einen Test schreiben, der als Spezifikation dient, also definiert, was der Code tun soll. Dies ist ein äußerst leistungsfähiges Softwareentwicklungskonzept, das jedoch häufig missbraucht wird. Typischerweise bedeutet testgetriebene Entwicklung, dass Unit-Tests als Leitfaden für die Erstellung von Anwendungscode verwendet werden. Tatsächlich kann dieser Ansatz jedoch auf jeder Ebene angewendet werden. In diesem Artikel gehen wir jedoch davon aus, dass wir Unit-Tests für unsere Anwendung verwenden. Der TDD-Ansatz stellt alles auf den Kopf, und anstatt zuerst Code zu schreiben und dann Unit-Tests zu schreiben, um diesen Code zu testen, schreiben Sie zuerst einen Unit-Test und dann Code, um diesen Test grün zu machen. Auf diese Weise „treiben“ Unit-Tests die Codeentwicklung voran. Dieser Vorgang wird immer wieder wiederholt. Sie schreiben einen weiteren Test, der mehr Funktionalität dessen definiert, was der Code tun soll. Anschließend schreiben und ändern Sie den Code, um sicherzustellen, dass der Test erfolgreich abgeschlossen wird. Sobald Sie ein grünes Ergebnis haben, beginnen Sie mit der Umgestaltung des Codes, d. h. mit der Umgestaltung oder Bereinigung, um ihn prägnanter zu gestalten. Diese Prozesskette wird oft als „Rot-Grün-Refactoring“ bezeichnet, da zunächst der Komponententest fehlschlägt (rot), dann der Code geschrieben wird, um ihn an den Test anzupassen und sicherzustellen, dass er erfolgreich ist (grün), und schließlich der Code optimiert wird ( Refactoring). .

Was ist das Ziel von TDD?

Was ist TDD und Unit-Testing [Übersetzung] - 10
Testgetriebene Entwicklung (TDD) kann wie Unit-Tests falsch verwendet werden. Es ist sehr einfach, das, was man tut, „TDD“ zu nennen und sogar der Praxis zu folgen, ohne zu verstehen, warum man es so macht. Der größte Nutzen von TDD besteht darin, dass Tests durchgeführt werden, um Qualitätsspezifikationen zu erstellen. Bei TDD handelt es sich im Wesentlichen um das Schreiben präziser Spezifikationen, die automatisch überprüft werden können, bevor der Code geschrieben wird. Tests sind die besten Spezifikationen, weil sie nicht lügen. Sie werden es Ihnen nach zwei Wochen der Qual nicht mit dem Code sagen: „Das habe ich überhaupt nicht gemeint.“ Tests werden, wenn sie richtig geschrieben sind, entweder bestanden oder nicht bestanden. Tests zeigen deutlich, was unter bestimmten Umständen passieren sollte. Ziel von TDD ist es daher, uns ein vollständiges Verständnis dessen zu vermitteln, was wir implementieren müssen, bevor wir mit der Implementierung beginnen. Wenn Sie mit TDD beginnen und nicht herausfinden können, was der Test testen soll, müssen Sie weitere Fragen stellen. Eine weitere wichtige Rolle von TDD besteht darin, Code zu bewahren und zu optimieren. Die Codepflege ist teuer. Ich scherze oft, dass der beste Programmierer derjenige ist, der den kürzesten Code schreibt, der ein Problem löst. Oder sogar derjenige, der beweist, dass dieses Problem nicht gelöst werden muss, und dadurch den Code vollständig entfernt, da dieser Programmierer den richtigen Weg gefunden hat, um die Anzahl der Fehler zu reduzieren und die Kosten für die Wartung der Anwendung zu senken. Durch die Verwendung von TDD können Sie absolut sicher sein, dass Sie keinen unnötigen Code schreiben, da Sie nur Code schreiben, um Tests zu bestehen. Es gibt ein Softwareentwicklungsprinzip namens YAGNI (Sie werden es nicht brauchen). TDD verhindert YAGNI.

Typischer TDD-Workflow (Test Driven Development).

Was ist TDD und Unit-Testing [Übersetzung] - 11
Aus rein akademischer Sicht ist es schwierig, die Bedeutung von TDD zu verstehen. Schauen wir uns also ein Beispiel einer TDD-Sitzung an. Stellen Sie sich vor, Sie setzen sich an Ihren Schreibtisch und entwerfen schnell einen Entwurf für eine Funktion, die es einem Benutzer ermöglicht, sich bei einer Anwendung anzumelden und sein Passwort zu ändern, wenn er es vergisst. Sie beschließen, mit der ersten Implementierung der Anmeldefunktion zu beginnen und eine Klasse zu erstellen, die die gesamte Logik für den Anmeldevorgang übernimmt. Sie öffnen Ihren bevorzugten Editor und erstellen einen Komponententest mit dem Namen „Leere Anmeldung verhindert, dass sich Benutzer anmelden.“ Sie schreiben Unit-Test-Code, der zunächst eine Instanz der Login-Klasse erstellt (die Sie noch nicht erstellt haben). Anschließend schreiben Sie Code, um eine Methode in der Login-Klasse aufzurufen, die einen leeren Benutzernamen und ein leeres Passwort übergibt. Abschließend schreiben Sie eine Prüfung anhand des erwarteten Ergebnisses und stellen sicher, dass der Benutzer mit einem leeren Login tatsächlich nicht angemeldet ist. Sie versuchen, einen Test auszuführen, aber er wird nicht einmal kompiliert, weil Sie keine Login-Klasse haben. Sie beheben diese Situation und erstellen eine Login-Klasse zusammen mit einer Methode in dieser Klasse zum Anmelden und einer weiteren zum Überprüfen des Status des Benutzers, um festzustellen, ob er angemeldet ist. Bisher haben Sie die Funktionalität dieser Klasse und die von uns benötigte Methode noch nicht implementiert. An dieser Stelle führen Sie den Test durch. Jetzt wird es kompiliert, schlägt jedoch sofort fehl.
Was ist TDD und Unit-Testing [Übersetzung] - 12
Jetzt kehren Sie zum Code zurück und implementieren die Funktionalität, um den Test zu bestehen. In unserem Fall bedeutet das, dass wir das Ergebnis erhalten sollten: „Der Benutzer ist nicht angemeldet.“ Sie führen den Test erneut durch und jetzt ist er erfolgreich. Kommen wir zum nächsten Test. Stellen wir uns nun vor, dass Sie einen Test mit dem Titel „Der Benutzer ist angemeldet, wenn er einen gültigen Benutzernamen und ein gültiges Passwort eingegeben hat“ schreiben müssen. Sie schreiben einen Komponententest, der die Login-Klasse instanziiert und versucht, sich mit einem Benutzernamen und Passwort anzumelden. In einem Unit-Test schreiben Sie eine Anweisung, dass die Login-Klasse die Frage, ob der Benutzer angemeldet ist, mit „Ja“ beantworten soll. Sie führen diesen neuen Test aus und er schlägt natürlich fehl, da Ihre Login-Klasse immer zurückgibt, dass der Benutzer nicht angemeldet ist. Sie kehren zu Ihrer Login-Klasse zurück und implementieren Code, um zu überprüfen, ob der Benutzer angemeldet ist. In diesem Fall müssen Sie herausfinden, wie Sie dieses Modul isolieren können. Der einfachste Weg, dies zu tun, besteht im Moment darin, den Benutzernamen und das Passwort, die Sie in Ihrem Test verwendet haben, fest zu codieren. Wenn sie übereinstimmen, wird das Ergebnis „Benutzer ist angemeldet“ zurückgegeben. Sie nehmen diese Änderung vor, führen beide Tests durch und beide bestehen den Test. Fahren wir mit dem letzten Schritt fort: Sie sehen sich den generierten Code an und suchen nach einer Möglichkeit, ihn neu zu organisieren und zu vereinfachen. Der TDD-Algorithmus lautet also:
  1. Habe einen Test erstellt.
  2. Wir haben Code für diesen Test geschrieben.
  3. Der Code wurde überarbeitet.

Schlussfolgerungen

Was ist TDD und Unit-Testing [Übersetzung] - 13
Das ist alles, was ich Ihnen zu diesem Zeitpunkt über Unit-Tests und TDD sagen wollte. Tatsächlich sind mit dem Versuch, Codemodule zu isolieren, viele Schwierigkeiten verbunden, da der Code sehr komplex und verwirrend sein kann. Nur sehr wenige Klassen existieren völlig isoliert. Stattdessen haben sie Abhängigkeiten, und diese Abhängigkeiten haben Abhängigkeiten und so weiter. Um mit solchen Situationen umzugehen, verwendet der TDD-Veteran Mocks, die dabei helfen, einzelne Klassen zu isolieren, indem sie Objekte in abhängigen Modulen ersetzen. Dieser Artikel ist nur ein Überblick und eine etwas vereinfachte Einführung in Unit-Tests und TDD. Wir werden nicht im Detail auf Dummy-Module und andere TDD-Techniken eingehen. Die Idee besteht darin, Ihnen die grundlegenden Konzepte und Prinzipien von TDD und Unit-Tests zu vermitteln, die Sie hoffentlich jetzt kennen. Original – https://simpleprogrammer.com/2017/01/30/tdd-unit-testing/