Schon seit einiger Zeit hege ich den Wunsch, funktionale Tests von newsbeuter durchzuführen, indem ich das ncurses-Interface von newsbeuter direkt treibe. Nur so ist es möglich, auch die ganze Logik, die zwangsweise im User Interface steckt, zu testen, und "Umfaller", wie sie in der Vergangenheit immer wieder passiert sind, künftighin zu erkennen. Nachdem ich für diese Aufgabe kein passendes Tool gefunden habe, bin ich nun endlich daran gegangen, selbst eine Lösung zu entwickeln. Herausgekommen ist "tuitest", ein Testing Tool für Text User Interfaces.
tuitest besteht aus zwei Komponenten, und zwar einerseits ein Recorder, der die Application under Test (AUT) in einem fixen 80x25-Terminal startet, und sämtliche Interaktionen mit der Applikation aufzeichnet, und daraus ein Ruby-Skript generiert, und andererseits ein Ruby-Modul, das Hilfsfunktionen zur Ausführung und Verifikation der Programmausgabe anbietet. Diese Hilfsfunktionen werden im vom Recorder generierten Skript verwendet.
Die Bedienung ist denkbar einfach: auf der Kommandozeile startet man den Recorder mit
tt-record scriptname.rb 'commandline mit parametern', bedient die Teile der AUT, die man testen will, und beendet sie wieder. Die aufgezeichnete Interaktion wird in scriptname.rb gespeichert, und kann über
ruby scriptname.rb sofort ausgeführt werden. Teile eines derartig erstellten Skripts sehen so aus:
Tuitest.keypress("r"[0])
Tuitest.wait(1244)
Tuitest.keypress(258)
Tuitest.wait(473)
Tuitest.keypress("r"[0])
Tuitest.wait(3453)
Tuitest.keypress(259)
Tuitest.wait(2215)
Tuitest.keypress(10)
Tuitest.wait(5702)
Tuitest.keypress("A"[0])
Tuitest.wait(980)
Automatisch generierte Verifications einfügen
Um das korrekte Verhalten des Programms anhand des User Interface auch überprüfen zu können, erlaubt tuitest, Verifications durchzuführen. Hierbei wird überprüft, ob sich an gewissen Positionen des Terminals ein bestimmter Text befindet. Schlägt diese Überprüfung fehl, so wird die Ausführung abgebrochen. Alternativ gibt es einen "soften" Ausführungsmodus, wo bei einer fehlgeschlagenen Überprüfung lediglich eine Warnung geloggt wird.
Verifications kann man entweder händisch nach Aufzeichnung des Skripts einfügen, oder aber vom Recorder automatisch generieren lassen: vor einer Operation, deren Ergebnis überprüft werden soll, nimmt man einen Snapshot des Terminals, mit Hilfe der Taste F5. Dann führt man die Operation aus. Ist diese beendet, so drückt man die Taste F6. Es wird wieder ein Snapshot genommen, und die Unterschiede zwischen dem vorigen Snapshot und dem letzten Snapshot werden als Verifications automatisch geskriptet. Diese generierten Verifications bilden i.A. eine gute Basis für weitere, manuelle Verfeinerungen. Verifications sehen folgendermaßen aus:
# begin auto-generated verification #1
verifier.expect(0, 65, "0 unread, 10 to")
verifier.expect(1, 5, " ")
verifier.expect(2, 5, " ")
verifier.expect(3, 5, " ")
verifier.expect(4, 5, " ")
verifier.expect(5, 5, " ")
verifier.expect(6, 5, " ")
verifier.expect(7, 5, " ")
verifier.expect(8, 5, " ")
verifier.expect(9, 5, " ")
verifier.expect(10, 5, " ")
# end auto-generated verification #1
Standardmäßig laufen die Verifications im "harten" Modus, d.h. ein Fehler führt zum Abbruch. Um den "soften" Modus zu aktivieren, ist als letzter Parameter zu einem verifier.expect Aufruf noch ":soft" (ohne Anführungszeichen) hinzuzufügen.
Die Ausführung schneller machen
Standardmäßig findet die Ausführung in exakt dem Timing statt, in dem auch die Aufzeichnung durchgeführt wurde. Oftmals will man dies für automatisierte Tests überhaupt nicht, denn diese sollen so schnell wie möglich ablaufen. tuitest bietet dafür Hilfsfunktionen, um manuelle Optimierungen möglich zu machen, und zwar mit einer Funktion
Tuitest.wait_until_idle. Diese Funktion wartet so lange, bis sich länger als eine Sekunde nichts am Terminal geändert hat. Sinnvoll ist, einen
Tuitest.wait_until_idle Call beim Programmstart einzufügen, dann die Key-Presses so schnell wie möglich auszuführen (d.h. die
Tuitest.wait Calls zu entfernen), und dann vor einer Verification wiederum mit
wait_until_idle zu warten, um der AUT die Möglichkeit zu geben, die extrem schnellen Eingaben "aufzuholen". Damit ist es möglich, die Ausführung deutlich schneller zu machen, und trotzdem die Stabilität der Tests nicht zu gefährden.
Wer sich mit tuitest spielen will, der muss sich bis jetzt mit der aktuellen Version aus dem
Subversion-Repository begnügen. Die Dokumentation ist bisher noch etwas dürftig, aufgezeichnete Skripte sollten aber halbwegs selbsterklärend sein. Und nachdem das Ausgabeformat ja ein Ruby-Skript ist, kann man damit mehr machen als nur Tests - im Grunde genommen sind jegliche Automatisierungen von v.a. ncurses-basierten Programmen möglich. Und wie immer ist Feedback herzlich willkommen.