TDD - test driven development

in #deutsch4 years ago

Im Artikel Code Retreat verwendete ich die Abkürzung TDD, hier möchte ich sie etwas näher beschreiben.

Ein Hinweis vorab
Dieser Artikel ist bewusst nicht-technisch geschrieben, da ich hier (auch) Nicht-Informatiker als Zielgruppe ansprechen möchte. Denn mit diesem Artikel verfolge ich drei Ziele:

  • Informatiker sollen hier einen ersten Impuls bekommen, um sich ohne große Einstiegshürde mit dem Thema TDD zu beschäftigen. (Wer sich genauer mit dem Thema beschäftigen will und weitere Artikel von mir darüber lesen möchte, kann dies gerne im Kommentar vermerken.)
  • Nicht-Informatiker sollen verstehen, warum die Programmierer in letzter Zeit bzw. zukünftig viel genauer wissen wollen, wie sich das Programm genau verhalten soll.
  • Die Kluft zwischen den Informatikern und Nicht-Informatikern soll verringert und das Verständnis füreinander verbessert werden, um den Frust auf beiden Seiten zu minimieren.

bisheriges Vorgehen
Bisher verlief die Softwareentwicklung oft so, dass erst das Produkt programmiert wurde und man das Produkt erst nach Beendigung der Programmierung testete. In der Regeln erfolgten diese Tests manuell und durch andere Personen als die Programmierer. Fanden die Tester einen Fehler, dann mussten die Entwickler diese mit mehr oder weniger großer Hektik ausbauen, weil man das "fertige Programm" ausliefern wollte und der Veröffentlichungstermin in der Regel nicht nur bereits feststand, sondern auch noch sehr zeitnah war, da man die Projektdauer unterschätzte.
Jede Änderung des Programms - und auch eine Fehlerbehebung ist eine solche Änderung! - birgt jedoch immer die Gefahr, dass dadurch andere Fehler durch sogenannte Seiteneffekte entstehen. Wenn bei erneuten Tests neue Fehler gefunden werden, kann sich jeder vorstellen, dass die Hektik nun noch größer wird.

Wie kann man diese Situation verbessern und warum sollte man TDD einsetzen?
TDD ist die Abkürzung für test driven development, also testgetriebene Entwicklung.
Hinter diesem sperrigen Begriff steckt die Überlegung, dass man sich zuerst überlegen soll, was das fertige Produkt können soll und wie man das gewünschte Verhalten testen kann.
Deshalb werden erst die Tests geschrieben und danach das Programm anhand der Tests entwickelt.
Um Fehler frühzeitig zu finden, sollte man sehr früh mit den Tests beginnen und diese Tests auch so häufig wie möglich wiederholen. Es werden also immer wieder die gleichen Testschritte wiederholt und geprüft, ob das gewünschte Ergebnis eintritt.
Immer wieder das Gleiche zu tun und immer das Ergebnis zu prüfen klingt nach einer typischen Aufgabe, die nach Automatisierung schreit. :-)
Deshalb werden die Tests nach Möglichkeit nicht manuell vorgenommen, sondern ebenfalls programmiert.

Die gängigen Entwicklungsumgebungen für viele Programmiersprachen bieten die Möglichkeit, Testcode zu schreiben, der bestimmte Funktionen des Programms testet.

Um mögliche Fehler genau eingrenzen zu können, bietet es sich an, die Tests möglichst klein zu schreiben. Dies hat außerdem den Vorteil, dass die Tests sehr übersichtlich sind und man deren Fehlerfreiheit leichter prüfen kann.
Denn Testcode ist nicht per Definition fehlerfrei, sondern kann selbstverständlich auch fehlerhaft sein!

Selbstverständlich kann und sollte man das Zusammenspielen von mehreren Funktionen ebenfalls testen. Man ordnet Tests deshalb hierarchisch folgendermaßen ein:

  • Mikrotests testen sehr kleine Funktionen (Methoden).
  • Modultest testen einen abgeschlossenen Funktionsteil testen.
  • Wenn größere Funktionsteile bzw. die gesamte Anwendung getestet werden soll, spricht man von Integrationstests.

Der Begriff "Unit-Tests" bezeichnet eigentlich nur den Modultest, wird aber oft für alle drei Testarten verwendet, da die mir bekannten Entwicklungsumgebungen nur Unit-Tests kennen. Da "Unit" eine "Einheit" bedeutet, reicht es aus, wenn man einfach die Einheit gedanklich anders fasst - beim Mikrotest betrachtet man die kleine Funktion als Einheit und beim Integrationstest eben das Programm.

Vorgehen nach TDD

  1. Definieren eines Testfalls und Schreiben eines entsprechenden Tests, um das gewünschte Ergebnis prüfen zu können.
    An Ende dieses Schritts muss der Test zwingend fehlschlagen! Warum? Weil zwar der Test existiert, aber noch kein Programm, dass diesen Test erfolgreich durchlaufen kann. Würde der Test nicht fehlschlagen, dann bräuchten wir ja gar kein Programm schreiben. :-)
    Da fehlgeschlagene Tests rot markiert werden, spricht man hier auch von einem "roten Test".
  2. Nun wird Programmcode geschrieben, um diesen Test möglichst schnell erfolgreich durchlaufen zu können, wobei alle anderen Tests natürlich auch weiterhin erfolgreich durchlaufen werden müssen! Weiter unten werde ich anhand eines Beispiels zeigen, wie eine solch kleine Lösung aussieht.
    Das Ziel dieses Schritts ist es, den Test so schnell wie möglich erfolgreich zu bestehen ("grün zu bekommen"), um (wieder) auslieferungsfähig zu sein.
    Es ist NICHT das Ziel dieses Schritts, schönen Programmcode zu haben. "Quick and dirty" ist also durchaus erlaubt.
  3. Nun kommt ein Schritt, der aus Zeitgründen gerne übersprungen wird - das sogenannte "Refaktorieren". Dabei geht es darum, den Programmcode des nun funktionierenden Programm wieder in eine gut wartbare Form zu bekommen. Dabei ist selbstverständlich darauf zu achten, dass alle Tests grün bleiben, das Programm also nach wie vor funktioniert. :-)
    Denn zu diesem Zeitpunkt funktioniert das Programm zwar, es ist aber code-technisch eher als Provisorium zu betrachten. Würde man mit diesem Stand weiterarbeiten, bekäme man mit der Zeit immer größere Probleme, um das Programm an zukünftige Anforderungen anpassen zu können. Man spricht hierbei auch von "technischen Schulden".
    Wenn man keine Verbesserungen mehr findet, beginnt man diesen Zyklus mit der nächsten Funktionalität.



Beispiel:

Wir möchten eine Funktion schreiben, die bei Übergabe des Zeichen "2" den Text "Es wurde eine gerade Zahl eingegeben" zurückgibt.

Beispielcode in C#:

public static string Zahlenpruefung(string eingabe)
{
    return "Es wurde eine gerade Zahl eingegeben";
}

Viele werden jetzt einwerfen, dass die Funktion ja die Eingabe überhaupt nicht prüft, sondern immer "gerade" zurückgibt! Man müsste doch erst einmal prüfen

  • ob überhaupt eine Zahl eingegeben wurde und anschließend
  • ob die Zahl ganzzahlig ist und anschließend
  • ob die Zahl gerade oder ungerade ist

Hier ist es wichtig, sich klarzumachen, dass es nur darum geht, dass bei Eingabe von "2" der gewünschte Text zurückkommt. Dieser Text wird also erfolgreich durchlaufen werden und damit ist der Schritt 2 beendet!
Klar ist das Beispiel sehr einfach, aber ich wollte es nicht unnötig komplex machen. Gehe einfach davon aus, dass es in der Realität nicht viel anders, sondern einfach nur viel größer ist.

Du hast hier große Bauchschmerzen weil Du große Probleme siehst? Keine Angst, die werden bald wieder kleiner. ;-)

An diesem Beispiel sieht man, wie einfach die Lösung sein kann. Im Schritt 3 geht es darum, so schnell wie möglich wieder auslieferungsfähig zu sein. Wenn z. B. in der Industrie eine Produktionsstraße stillsteht, kostet jede Minute richtig viel Geld. Sobald das Programm wieder funktioniert und damit die Produktionsstraße wieder läuft, sollte man sich natürlich Gedanken machen, ob dieses Provisorium so bleiben soll.

Wer nun sagt, wir haben keine teure Produktionsstraße, weil wir in einem Startup arbeiten, soll sich folgendes Szenario vorstellen:
Die erste oder nächste Finanzierungsrunde steht an und da gerade Ebbe in der Kasse ist, muss möglichst schnell Geld rein, weshalb beim nächsten Termin alles passen muss. Kurz vor dem Termin, an dem Ihr Euer Produkt präsentiert sollt, fällt Euch ein Fehler oder eine fehlende Funktionalität auf. Jetzt könnt Ihr entweder dafür sorgen, dass Euer Programm zum Termin funktioniert oder Ihr könnt eben nichts zeigen und hofft, dass Ihr trotz Nicht-Präsentation die erhoffte Finanzierung bekommt. Falls Ihr die zweite Option wählt - viel Glück! :-)

Zurück zu den Bauchschmerzen ;-)
An diesem Beispiel sieht man aber auch, dass man sich genau überlegen soll, welche Fälle man abtesten muss und wie schnell man technische Schulden erzeugen kann.
Wird die Funktion nur mit gültigen Werten versorgt (während der Präsentation bei der Finanzierungsrunde), dann funktioniert anscheinend alles.
Um nun auch ungerade Zahlen ausgeben zu können, muss die Funktion nun rechnen können und vorher erst einmal sichergestellt werden, dass auch wirklich eine Zahl übergeben wird. Damit erhöht sich der Aufwand deutlich im Vergleich zum Beispiel, in dem einfach eine Zeichenfolge zurückgegeben wird.
Dies zu erkennen und bei der weiteren Entwicklung zu berücksichtigen, halte ich für extrem wichtig!

Sort:  

Guten Tag,

Mein Name ist GermanBot und du hast von mir ein Upvote erhalten. Als UpvoteBot möchte ich dich und dein sehr schönen Beitrag unterstützen. Jeden Tag erscheint ein Voting Report um 19 Uhr, in dem dein Beitrag mit aufgelistet wird.
In dem Voting Report kannst du auch vieles von mir erfahren, auch werden meine Unterstützer mit erwähnt.
Schau mal bei mir vorbei, hier die Votings Reports.
Mach weiter so, denn ich schaue öfter bei dir vorbei.

Euer

GermanBot

Coin Marketplace

STEEM 0.18
TRX 0.05
JST 0.022
BTC 16996.23
ETH 1256.31
USDT 1.00
SBD 2.11