Einfache Testautomatisierung mit Robot Framework
Testing ist wichtig. Denn nur so kann man sich einen Überblick über die Funktionsfähigkeit eines Produkts verschaffen. Trotzdem wird in der Entwicklung eines neuen Produkts das Testing oftmals vernachlässigt. Die Entwicklung und Ausführung von Tests brauchen Zeit. Und so lange Tests durchwegs erfolgreich, sprich „passed“, verlaufen, ist deren Mehrwert nicht offensichtlich. Testautomatisierung hingegen verspricht den Aufwand für die Testdurchführung zu reduzieren, wird jedoch oft als sehr aufwändig wahrgenommen. Sinnvoll wäre, wenn jeder Softwareentwickler einfach Tests für neue Funktionen erfassen, und diese bei jeder Code-Änderung ausführen lassen könnte. Gerade in der agilen Softwareentwicklung sind Regressionstests sehr wertvoll. Denn so kann die Funktionstüchtigkeit der Software kontinuierlich gewährleistet werden.
Robot Framework kann für Behavior-Driven Integrations- und Systemtests eine gute Basis sein. Der folgende Artikel soll zeigen, wie einfach man eine beliebige Python-Bibliothek einbinden und damit auch komplexe Testabläufe verständlich erfassen kann.
Ziel: Ein Testender soll gleich schnell (oder schneller) einen Test in Robot Framework beschreiben, als diesen manuell auszuführen.
Vorteil: Jeder Test kann ohne Mehraufwand als Regressionstest verwendet werden.
Wieso Robot Framework?
Es gibt eine Vielzahl von Test Frameworks. Aber was macht Robot Framework interessant?
- Einfaches Einbinden von beliebigen Python-Bibliotheken
- Tests können gut lesbar beschrieben werden und sind entsprechend gut wartbar
- Bei jeder Ausführung wird ein ausführlicher Log und Report in HTML erzeugt
- Tests sind in Text-Dateien definiert, welche entsprechend versionierbar sind
- Oft benutzte Test-Logik ist out-of-the-box vorhanden
- Viele zusätzliche Bibliotheken verfügbar für Website-Testing, Datenbank-Schnittstellen…
- Continuous Integration mit Plugins für Jenkins, Bamboo…
- Open Source, Apache License 2.0
Eigener Python-Code in Robot Framework
Begrifflichkeiten und Hierarchie
Tests werden auf einem Host gestartet, welcher Befehle mit einem Target (DUT, Device under Test) austauscht.
- Library: Bildet eine Schnittstelle zum Target in Python (oder Java) ab
- Keyword: Ablauf in Robot Framework, bestehend aus anderen Keywords und / oder Aufrufen von Library-Funktionen
- Test Case: Ein Ablauf von Keywords, welcher erfolgreich durchgeführt können werden muss
- Test Suite: Eine Sammlung von Test Cases (oder eine Sammlung von Test Suites)
Installation
Sobald Python auf dem Host installiert ist, lässt sich Robot Framework mit folgendem Befehl installieren:
pip install robotframework
Syntax
Die Test Suites werden in .robot-files definiert und mit *** Bereichname *** unterteilt. Die Unterscheidung zwischen Name von Test Cases / Keywords und deren Inhalt wird, analog zu Python, durch Einrücken definiert.
Keywords und deren Argumente werden durch mind. 2 Leerzeichen getrennt. Dies ist für Softwareentwickler sehr gewöhnungsbedürftig. Aber es erlaubt eine sehr einfaches String-Handling und gut lesbare Keyword-Namen.
Syntax-Highlighting für Robot Framework wird per Plugins in vielen beliebten Editoren unterstützt (Eclipse, IntelliJ IDEA, Notepad++ …).
Minimal-Beispiel: Aufruf von Python-Code in Robot Framework
class LibDummy: # wie Dateiname def lib_dummy_assert(self): assert 1 == 1
*** Settings *** Library ./LibDummy.py *** Test Cases *** Call LibDummy lib_dummy_assert
Der Overhead für Robot Framework auf Seiten der Python-Bibliothek ist also minimal. Um nun die Test-Suite MinimalSuite.robot auszuführen, gibt man folgenden Befehl auf der Kommandozeile ein:
robot MinimalSuite.robot
Bereits auf der Konsole sieht man, ob die Tests erfolgreich waren. Die Resultate spiegeln sich auch im Return Code wieder. Ausserdem werden ein detailliertes log.html und eine Zusammenfassung in report.html erzeugt.
Die Rohdaten der Resultate werden in output.xml gespeichert. Diese Datei lässt sich dann in Jenkins o.Ä. parsen.
Beispiel: Serielle Schnittstelle in Robot
Angenommen, wir haben ein Gerät über COM5 verbunden. Wenn wir dort GET_SETPOINT senden, erhalten wir die aktuelle Einstellung als Antwort. Wir können mit SET_SETPOINT:300 die Einstellung auf 300 ändern und bekommen als Antwort OK zurück, wenn dies erfolgreich war. Wie können wir dieses Gerät nun per Robot Framework steuern?
import serial # http://pyserial.readthedocs.io/en/latest/index.html class LibSerial: def __init__(self, serial_port): self.__serial = serial.Serial(serial_port) # open serial port def lib_serial_set(self, key, value): self.__serial.write("SET_" + key + ":" + str(value) + "\n") ret = self.__serial.readline() assert ret == "OK\n" # target returns 'OK' if command was valid def lib_serial_get(self, key): self.__serial.write("GET_" + key + "\n") ret = self.__serial.readline() return ret[:-1] # cut off \n of returned value def lib_serial_close(self): self.__serial.close()
*** Settings *** Documentation Configure Setpoint over Serial Connection using SET_SETPOINT and GET_SETPOINT Library ./LibSerial.py COM5 Suite Teardown lib_serial_close *** Test Cases *** Configure Setpoint and Read It Back Set Setpoint 50 Setpoint Should Be 50 Configure Invalid Setpoint Expect Error When Configuring Setpoint -10 *** Keywords *** Set Setpoint [Arguments] ${sp} lib_serial_set SETPOINT ${sp} Setpoint Should Be [Arguments] ${sp} ${read_sp}= lib_serial_get SETPOINT Should Be Equal As Numbers ${read_sp} ${sp} Expected Setpoint ${sp} but got ${read_sp} Expect Error When Configuring Setpoint [Arguments] ${sp} Run Keyword And Expect Error AssertionError Set Setpoint ${sp}
Und was ist so toll daran?
Man sieht, dass man sehr einfach eine Bibliothek in gut lesbaren Test Cases ansteuern kann. Robot Framework handhabt dabei die übliche Test-Logik. Wenn beispielsweise der erste Test fehlschlägt, wird dieser entsprechend markiert, doch der zweite Test wird dennoch ausgeführt. Ausserdem generiert Robot Framework stets detaillierte, standardisierte Ausgaben.
In diesem Beispiel sehen wir auch, wie Abläufe in gut lesbare Keywords zusammengefasst werden können und wie man diesen Argumente übergibt. Das Protokoll für die Steuerung wird für den Verfasser des Tests abstrahiert. Spätere Tests können nun komfortable, einfach verständliche Keywords wie Setpoint Should Be benutzen. Keywords können wiederum andere Keywords aufrufen und so komplexe Abläufe zusammenfassen, z.B. Update System to latest Version and load default Configuration .
Der besondere Syntax erlaubt, dass die Keywords Leerzeichen beinhalten und man Strings ohne besondere Operatoren zusammensetzen kann (z.B. Expected Setpoint ${sp} )
Oftmals bieten Hersteller von Geräten bereits eine Python-Bibliothek an. In diesen Fällen kann man entsprechend schnell diese in das Framework einbinden. Falls keine bestehende Bibliothek zur Verfügung steht, lassen sich proprietäre Kommunikationsprotokolle über serielle Schnittstellen oder TCP-Sockets verhältnismässig einfach in Python abbilden.
Wie weiter?
Bisher haben wir gesehen, wie einfach eine beliebige Python-Bibliothek in Robot Framework eingebunden werden kann. Nun können wir mit den eingebauten Funktionen von Robot Framework eine komplette Testumgebung aufbauen. Anbei seien einige Möglichkeiten aufgelistet, welche das Framework bietet:
- Keywords als Resource-Files in verschiedenen Test Suiten importieren und verwenden
- Variablen (z.B. Zieladresse des Targets…) als Resource-Files importieren oder über die Kommandozeile setzen
- Mit Setup und Teardown definieren, was beim Beginn oder Ende eines Test Cases oder einer Test Suite ausgeführt wird
- Metadaten können während der Ausführung nach Belieben erfasst werden (z.B. Seriennummer des Targets, SW-Version…)
- Test Suites in Ordnern hierarchisch gliedern
- Test Cases mit Tags filtern
- Fertige Bibliotheken für Website-Testing, Datenbank-Zugriffe, FTP, SSH, … einbinden
- Data-driven Tests formulieren
- Tests von Skripts (z.B. Python) generieren, um so die Tests aktuell zu halten und die Coverage zu erhöhen
- Log-Einträge während der Ausführung mit HTML formatieren um so auch Graphen etc. in Logs ablegen
- Post-Processing von Testresultaten (Zusammenführen von verschiedenen Durchläufen, Filtern…)
- Dokumentationen generieren
Weiterführende Informationen finden sich auf der Robot-Framework-Homepage, dem User-Guide oder der Dokumentation der BuiltIn-Library.
Robot Framework ist also in der Lage, komplexe Testaufbauten abzubilden, erspart aber die mühselige Eigenentwicklung von häufig verwendeten Test-Funktionen.
Worauf man bei „guten“ Tests achten sollte
Sie sehen, durch die Testautomatisierung eröffnen sich unzählige Möglichkeiten. Damit man sich nicht verliert, kann es hilfreich sein, sich an folgenden Punkten zu orientieren:
- Ein stabiler, reproduzierbarer Startzustand ist sehr wichtig. Um unbeabsichtigte Abhängigkeiten zu vermeiden, sollte im Setup jedes Tests alle nötigen Einstellungen des Targets vorgenommen werden.
- Ein Test Case sollte unabhängig von anderen Test Cases sein. D.h. auch wenn man nur ein einzelner Test Case ausführt, muss dessen Setup und Teardown dafür sorgen, dass der Test erfolgreich ist. Der Zustand des Targets zu Beginn darf dabei keine Rolle spielen.
- Bei der Integration in Continuous Integration ist es ratsam, möglichst früh einfache, stabile Tests in den Workflow zu integrieren. Man kann dann schrittweise mehr und komplexere Tests hinzufügen. So gewöhnt sich das Team an den Workflow und kann kontrollierter eigene Tests entwickeln.
- Sleep ist oft ineffizient, da man auch Spezialfälle berücksichtigen muss (temporär hohe Last beim Host oder Target…). Wait Until Keyword Succeeds (Dokumentation) erlaubt hier schnellere Reaktionen.
Man sollte sich auch Gedanken über die Grenzen der Testautomatisierung mit Robot Framework machen. Schwierig zu automatisierende Tests können auch nach wie vor (halb-)manuell ausgeführt werden, z.B. mit Dialogs. Unit-Tests sollen mit Integrations- und Systemtests nicht ersetzt, sondern ergänzt werden.