Im Folgenden will ich die Anwednung von Testgetriebener Entwicklung (Test Driven Development, üblicherweise nur mit TDD benannt) zeigen.
Für einen detailierte Beschreibung von TDD sei einem zB der Wiki-Artikel und entsprechende Literatur ans Herz gelegt, besonders natürich Test Driven Development by Example von Kent Beck.
Anders als im sonst häufig anzutreffenden Entwickelungsprozess, bei der man erst den Code und danach Tests dazu schreibt, kehrt man dies bei TDD um.
Als Erstes, noch bevor man überhaupt Code schreibt, schreibt man immer einen Test – und erst wenn ein Test geschrieben ist, wird der dafür nötige Code geschrieben.
Dabei folgt man drei Schritten, die man solange wiederholt, bis die gesamte gewünschte Funktionalität (oder anders: bis einem keine Testfälle mehr einfallen, die fehlschlagen könnten) implementiert ist:
1. Tests erstellen: Man fügt einen Tests hinzu, welcher Funktionalität erfordert, die bisher noch nicht erfüllt ist – dieser Test schlägt also immer fehl (als fehlschlagender Test gelten dabei auch Compiler-Fehler, zB weil eine Methode fehlt). Dabei sollte man jeweils den einfachst möglichen Testfall hinzufügen. Nach diesem Schritt schlägen mindestens ein Test fehl.
2. Funktionalität implementieren: Man verändert nun den Code, sodass der Tests erfüllt ist (je nach Änderungen muss man dabei auch die vorherigen Tests anpassen, wichtig ist dabei, diese nicht einfach zu entfernen). Dabei folgt man KISS und YAGNI, man fügt grad so viel Funktionalität hinzu, dass die Tests erfüllt sind, aber nicht mehr. Nach diesem Schritt müssen alle Tests erfolgreich durchlaufen.
3. Refactoring: Man überarbeitet den bisherigen Code, man fasst doppelte Dinge zusammen, entfernt überflüssiges, passt den Code-Stil an etc. Durch die Tests ist man dabei abgesichert, dass man nichts kaputt macht. Auch nach diesem Schritt müssen weiterhin alle Tests erfolgreich durchlaufen. Danach startet man wieder mit Schritt 1. (Wenn man sehr strikt ist, entsteht der eigentliche Code auch erst durch das Refactoring, in dem man Dinge in Methoden und Klassen auslagert, das werde ich hier aber nicht machen.)
Das Verfahren bietet mehrere Vorteile:
* Man macht sich mehr Gedanken über das Design, es entsteht nicht "einfach so"
* Jede Funktionalität ist immer durch eine konkrete Anforderung entstanden
* Die Tests fungieren gleichzeitig als Spezifikation und Dokumentation
* Der gesamte Code ist immer von Tests abgedeckt, bei Änderungen ist man dadurch abgesichert
Als Beispiel, um TDD zu demonstrieren, nutze ich hier FizzBuzz (u.U. gibts später noch ein Beispiel mit einer Pfadsuche, was etwas komplexer ist, hängt davon ab, ob ich den Code wiederfinde ).
FizzBuzz ist ein einfaches Spiel, bei der man Zahlen ab Eins hochzählt. Für ein paar Zahlen gelten dabei Besonderheiten: wenn eine Zahl durch drei Teilbar ist, sagt man stattdessen "Fizz", wenn eine durch 5 teilbar ist "Buzz", und wenn sie durch beides teilbar ist beides, also "FizzBuzz".
Das ganze Beispiel wird *sehr* kleinschrittig, um das Verfahren zu zeigen. Manchen werden die Schritte sicherlich zu klein sein, und in der "echten Welt" werden viele es anders lösen, zur Demonstration ist es aber mit Absicht so gemacht
Der Code dazu findet sich unter https://gitlab.com/mrBrown/fizzbuzz
Also, anfangen mit der ersten Interation der drei Schritte: (https://gitlab.com/mrBrown/fizzbuzz/-/commit/a283897ae99a6ccf1de8941b45516d4779212d1e)
Schritt 1:
Wir starten erstmal bei Null, mit dem allereinfachsten Testfall. Bei FizzBuzz geht es ums Hochzählen, und was ist dabei "das wenigste" was man machen kann? Einfach gar nicht erst anfangen zu zählen, also quasi "bis 0" zählen. (Diese Regel findet man bei den meisten Tests, egal zu welchem Thema, zB in Form einer leeren Liste, eines leeren Strings, einer 0, ...)
Wenn man ab 1 hochzählt und dabei "bis 0" zählt, was ist dann das Ergebnis? Einfach nichts.
Und wie könnte man das in Java am einfachsten ausdrücken? Einfach als
Außerdem brauchen wir irgendeine Methode, die wir ausführen können, um das Ergebnis zu erhalten, und für eine Methode natürlich auch eine Klasse:
Aus diesem bauen wir jetzt den ersten Testfall:
Schritt 2:
Der Code dazu ist ziemlich einfach, wir erstellen die Klasse mit der Methode und geben einfach nur null zurück – der Test ist danach erfolgreich:
Für einen detailierte Beschreibung von TDD sei einem zB der Wiki-Artikel und entsprechende Literatur ans Herz gelegt, besonders natürich Test Driven Development by Example von Kent Beck.
Anders als im sonst häufig anzutreffenden Entwickelungsprozess, bei der man erst den Code und danach Tests dazu schreibt, kehrt man dies bei TDD um.
Als Erstes, noch bevor man überhaupt Code schreibt, schreibt man immer einen Test – und erst wenn ein Test geschrieben ist, wird der dafür nötige Code geschrieben.
Dabei folgt man drei Schritten, die man solange wiederholt, bis die gesamte gewünschte Funktionalität (oder anders: bis einem keine Testfälle mehr einfallen, die fehlschlagen könnten) implementiert ist:
1. Tests erstellen: Man fügt einen Tests hinzu, welcher Funktionalität erfordert, die bisher noch nicht erfüllt ist – dieser Test schlägt also immer fehl (als fehlschlagender Test gelten dabei auch Compiler-Fehler, zB weil eine Methode fehlt). Dabei sollte man jeweils den einfachst möglichen Testfall hinzufügen. Nach diesem Schritt schlägen mindestens ein Test fehl.
2. Funktionalität implementieren: Man verändert nun den Code, sodass der Tests erfüllt ist (je nach Änderungen muss man dabei auch die vorherigen Tests anpassen, wichtig ist dabei, diese nicht einfach zu entfernen). Dabei folgt man KISS und YAGNI, man fügt grad so viel Funktionalität hinzu, dass die Tests erfüllt sind, aber nicht mehr. Nach diesem Schritt müssen alle Tests erfolgreich durchlaufen.
3. Refactoring: Man überarbeitet den bisherigen Code, man fasst doppelte Dinge zusammen, entfernt überflüssiges, passt den Code-Stil an etc. Durch die Tests ist man dabei abgesichert, dass man nichts kaputt macht. Auch nach diesem Schritt müssen weiterhin alle Tests erfolgreich durchlaufen. Danach startet man wieder mit Schritt 1. (Wenn man sehr strikt ist, entsteht der eigentliche Code auch erst durch das Refactoring, in dem man Dinge in Methoden und Klassen auslagert, das werde ich hier aber nicht machen.)
Das Verfahren bietet mehrere Vorteile:
* Man macht sich mehr Gedanken über das Design, es entsteht nicht "einfach so"
* Jede Funktionalität ist immer durch eine konkrete Anforderung entstanden
* Die Tests fungieren gleichzeitig als Spezifikation und Dokumentation
* Der gesamte Code ist immer von Tests abgedeckt, bei Änderungen ist man dadurch abgesichert
Als Beispiel, um TDD zu demonstrieren, nutze ich hier FizzBuzz (u.U. gibts später noch ein Beispiel mit einer Pfadsuche, was etwas komplexer ist, hängt davon ab, ob ich den Code wiederfinde ).
FizzBuzz ist ein einfaches Spiel, bei der man Zahlen ab Eins hochzählt. Für ein paar Zahlen gelten dabei Besonderheiten: wenn eine Zahl durch drei Teilbar ist, sagt man stattdessen "Fizz", wenn eine durch 5 teilbar ist "Buzz", und wenn sie durch beides teilbar ist beides, also "FizzBuzz".
Das ganze Beispiel wird *sehr* kleinschrittig, um das Verfahren zu zeigen. Manchen werden die Schritte sicherlich zu klein sein, und in der "echten Welt" werden viele es anders lösen, zur Demonstration ist es aber mit Absicht so gemacht
Der Code dazu findet sich unter https://gitlab.com/mrBrown/fizzbuzz
Also, anfangen mit der ersten Interation der drei Schritte: (https://gitlab.com/mrBrown/fizzbuzz/-/commit/a283897ae99a6ccf1de8941b45516d4779212d1e)
Schritt 1:
Wir starten erstmal bei Null, mit dem allereinfachsten Testfall. Bei FizzBuzz geht es ums Hochzählen, und was ist dabei "das wenigste" was man machen kann? Einfach gar nicht erst anfangen zu zählen, also quasi "bis 0" zählen. (Diese Regel findet man bei den meisten Tests, egal zu welchem Thema, zB in Form einer leeren Liste, eines leeren Strings, einer 0, ...)
Wenn man ab 1 hochzählt und dabei "bis 0" zählt, was ist dann das Ergebnis? Einfach nichts.
Und wie könnte man das in Java am einfachsten ausdrücken? Einfach als
null
Außerdem brauchen wir irgendeine Methode, die wir ausführen können, um das Ergebnis zu erhalten, und für eine Methode natürlich auch eine Klasse:
Aus diesem bauen wir jetzt den ersten Testfall:
Java:
@Test
void countToZero() {
Object result = FizzBuzz.generate();
assertNull(result);
}
Schritt 2:
Der Code dazu ist ziemlich einfach, wir erstellen die Klasse mit der Methode und geben einfach nur null zurück – der Test ist danach erfolgreich:
Java:
public class FizzBuzz {
public static Object generate() {
return null;
}
}