TDD ist ein Softwareentwicklungsprozess, bei dem die Software durch zuvor geschriebene Tests getrieben wird (Test-First-Ansatz).
TDD besteht grundsätzlich aus drei Schritten, die man wiederholt durchführt:
- Schreibe einen Test für das nächste Stück Funktionalität, das du hinzufügen möchtest.
- Schreibe den funktionalen Code, bis der Test besteht.
- Refaktoriere sowohl den neuen als auch den alten Code, damit er gut strukturiert ist.

(Quelle: https://commons.wikimedia.org/wiki/File:TDD_Global_Lifecycle.png)
Die Illustration zeigt, wie diese drei Schritte einen TDD-Zyklus bilden. Zuerst schreibt man den Test, der fehlschlagen soll, und dann lässt man den Test mit möglichst wenig Aufwand und Code bestehen. Wenn der Test erfolgreich war, prüft man, ob alle anderen Tests, die man zuvor geschrieben hat, ebenfalls noch bestehen. Wenn das der Fall ist, sucht man nach Code, der refaktoriert werden kann. Wenn nicht alle Tests bestehen, muss man die Regressionen korrigieren, bis alle Tests bestehen. Dann erfolgt das Refactoring.
Wenn man Tests für seine Software schreibt, konzentriert man sich auf die Funktionalität der Software und nicht auf ihre Implementierungsdetails. Tests können auch als formale Anforderungsspezifikation für die Software dienen und anderen Entwicklern (und auch Nicht-Entwicklern) helfen, den Zweck des Codes zu verstehen.
Test Driven Development führt zu saubererem und verständlicherem Code, da man in jedem TDD-Zyklus refaktoriert.
Man verfolgt auch einen konzeptionelleren Entwicklungsansatz, weil man sich in jedem Zyklus nur auf eine Sache konzentriert. Daher ist der Entwicklungsprozess in der Regel strukturierter als ein Ansatz ohne Test-First.
Test-Driven Development und Model-Driven Software Development
Im Kontext der modellgetriebenen Softwareentwicklung konzentriert man sich jedoch nicht auf das Testen des implementierenden Codes, der in der Regel automatisch aus dem Modell generiert wird. Wir erwarten, dass der Code-Generator korrekt funktioniert und von seinen Entwicklern getestet wird. (Natürlich kann man trotzdem den generierten Code testen und entsprechende Tests für die Implementierungssprache schreiben.)
Wenn man sich also nicht auf das Testen des Codes konzentriert – worauf konzentriert man sich dann stattdessen bei der testgetriebenen Modellentwicklung?
Beim testgetriebenen Software-Modeling liegt der Fokus auf dem Testen der Funktionalität des Modells unter Test.
Wenn das Modell beispielsweise ein Zustandsautomat ist, würde man sich wahrscheinlich auf folgende Punkte konzentrieren:
- Enthält das Modell alle Transitionen, die benötigt werden, oder fehlen Transitionen?
- Hat man die richtigen Guards für die Transitionen?
- Wurden alle benötigten Zustände einbezogen?
- Funktionieren alle Zustandsänderungen korrekt?
Es gibt definitiv noch mehr Dinge, die man beim Modellieren und Entwickeln eines Zustandsautomaten prüfen möchte.
TDD auf die modellgetriebene Softwareentwicklung anzuwenden bedeutet, das Modell inkrementell zu entwickeln, indem man zuerst einen Test schreibt und dann das Modell erweitert, um den Test zu erfüllen. In einem anschließenden Refactoring-Schritt versucht man in der Regel, die Komplexität des Modells zu vereinfachen, während man alle Tests weiterhin erfüllt.
Tests dienen auch als Dokumentation für den Zustandsautomaten. Wenn man etwas ändern muss, sind die Tests eine Absicherung, und man kann jederzeit prüfen, ob der Zustandsautomat noch wie erwartet funktioniert.
Das SCTUnit-Framework
Um zu zeigen, wie MDSD kombiniert mit TDD aussehen könnte, gebe ich ein Beispiel für itemis CREATE und sein Unit-Testing-Framework SCTUnit.
SCTUnit ist ein Unit-Testing-Framework für Zustandsautomaten und Teil der Standard Edition von itemis CREATE. Es ermöglicht die testgetriebene Entwicklung von Statechart-Modellen auf deren semantischer Ebene. Es generiert Unit-Tests für verschiedene Plattformen wie C, C++ oder Java. Die Tests können auch in der Simulator-Ansicht ausgeführt werden.
In diesem Beispiel erstellen wir einen einfachen Lichtschalter, der ein- und ausgeschaltet werden kann.
Wir beginnen damit, eine SCTUnit-Datei und ein leeres Statechart-Modell zu erstellen und schreiben einen Test für einen initialen Zustand:

Wie man sieht, enthält der Test Fehler, weil kein “Off”-Zustand definiert ist und der Test daher nicht ausführbar ist. Gemäß dem TDD-Zyklus ist der nächste Schritt, den Test mit möglichst wenig Aufwand grün zu machen – deshalb erstellen wir einen “Off”-Zustand im Statechart-Modell:

SCTUnit bietet die Möglichkeit, die SCTUnit-Tests wie einen JUnit-Test auszuführen, um zu prüfen, ob der Test immer noch fehlschlägt oder ob er besteht:

Da Modell und Test im Moment noch sehr einfach sind, können wir nichts refaktorieren und beginnen daher den zweiten Zyklus.
Wir möchten nun testen, dass der Zustandsautomat seinen aktiven Zustand von “Off” auf “On” ändert, wenn wir das Event “operate” auslösen:

Der Test enthält Fehler, weil weder ein Event “operate” noch ein Zustand “On” im Statechart-Modell definiert sind. Fügen wir sie also dem Modell hinzu:

Der nächste Schritt ist zu prüfen, ob diese Änderungen die Tests grün gemacht haben:

Nachdem wir den Zustand “On” und die Transition von “Off” nach “On” modelliert haben, möchten wir nun modellieren, dass der Zustand “On” wieder zu “Off” wechselt, wenn wir das Event “operate” erneut auslösen. Was tun wir also? Richtig, wir schreiben zuerst einen Test:

Wie man sieht, gibt es keine Fehler in der SCTUnit-Datei, also führen wir den Test aus und schauen, ob er grün ist:

Keine Überraschung: Der Test ist nicht grün, weil wir keine Transition von “On” nach “Off” hinzugefügt haben, die beim Auslösen des Events “operate” aktiviert werden würde. Fügen wir also diese Transition dem Modell hinzu:

Wenn man die Tests erneut ausführt, ist das Ergebnis nun grün:

Schaut man sich die SCTUnit-Datei noch einmal an, sieht man, dass man die Tests etwas refaktorieren kann. Das Code-Fragment
raise operate
cycle
ist in jedem Test redundant, also extrahieren wir dieses Fragment in eine eigene Operation:

Damit haben wir einen Lichtschalter-Zustandsautomaten vollständig testgetrieben entwickelt/modelliert.
Das war natürlich nur ein kurzer Überblick über SCTUnit. Das Framework bietet weit mehr Funktionen, die hier nicht behandelt wurden, wie zum Beispiel:
- Testklassen in Testsuiten gruppieren
- Rückgabewerte von Operationen mocken
- Prüfen, wie oft Operationen aufgerufen wurden
- Kontrollstrukturen wie Schleifen und If-Else verwenden
- Virtuelle Zeit für das sofortige Testen von Zeitauslösern
- Und vieles mehr…
Für weitere Informationen lesen Sie bitte unsere Dokumentation. Das Testing-Framework befindet sich derzeit in seiner Betaphase, was bedeutet, dass wir noch intensiv daran arbeiten und – wie immer – über jedes Feedback froh sind. Also probieren Sie es aus und sagen Sie uns, was Sie denken!
Model-Driven Software Development bei itemis — Wir helfen Teams, zuverlässige Software durch modellbasierte Ansätze zu entwickeln: Model-Driven Software Development →