Vollautomatisches Regressionstesten White Paper ReTest 0721-72380106 / info@retest.de / www.retest.de Dr. Jeremias Rößler Foto by Jorrit Krijnen
Einführung Wenn Software geändert oder erweitert wird, dann dürfen dabei keine Fehler oder unerwünschten Seiteneffekte (Regressionen) eingeführt werden bereits bestehende Funktionalität darf nicht beeinträchtigt werden. Um dies sicherzustellen muss auch der ungeänderte Teil der Software immer mitgetestet werden (Regressionstesten). Dazu werden häufig automatisiert ablaufende Regressionstests manuell erstellt, entweder via Capture/Replay, mittels Test Automation Frameworks oder modellbasiert. Capture/Replay-Technologien sind sehr fragil. Bereits bei kleinen Änderungen muss ein Test neu aufgezeichnet werden was wiederum manuellen Aufwand bedeutet. Um dieses Problem zu lösen wurden Test Automation Frameworks eingeführt: die Testskripte werden mittels Programmierung modularisiert und generalisiert und damit theoretisch robuster gegen Änderungen bzw. diese werden einfacher durchführbar. Aber während das Capturing bei Capture/Replay-Technologien von rein fachlichen Anwendern ausgeführt werden kann, werden bei Test Automation Frameworks die Tests wie die Software selbst von Programmierern entwickelt wodurch man letztendlich zwei parallel laufende Entwicklungsprojekte hat. Je höher die angestrebte Testabdeckung, desto größer der Aufwand bei der Erstellung und Wartung. Dabei ergibt sich bei der Festlegung der Prüfregeln des erwarteten Ergebnisses (häufig in der Form von Assertions) eine Kostenabwägung: je mehr Prüfregeln erstellt werden, desto mehr Pflegeaufwand entsteht bei absichtlichen Änderungen weil die Prüfregeln angepasst werden müssen aber je weniger Prüfregeln, desto größer die Wahrscheinlichkeit, dass ein Fehler unbemerkt bleibt. Aus all diesen Gründen sind durchschnittlich nur ca. 15% der Regressionstests automatisiert 1, wodurch Regressionstesten in der Praxis großen manuellen Aufwand und damit hohe Kosten verursacht. Infolgedessen wird häufig nur vereinzelt und zu bestimmten Zeitpunkten auf Regressionen getestet (etwa direkt vor einer Abnahme). Der tatsächliche Projektfortschritt und der verbleibende Restaufwand werden aber oft erst mit der Anzahl der beim Testen gefundenen Fehler sichtbar, was das Projektrisiko erhöht und die Ergebnisqualität senkt. Werden kritische Fehler erst kurz vor einem Termin (Meilenstein, Review, Abnahme) entdeckt, so bedeutet die Fehlerbehebung für Entwickler wie Projektleiter gleichermaßen großen Projektdruck und Überbelastung. Ohne Tests sind die Auswirkungen einer weitreichenden Änderung für den einzelnen Entwickler schlecht abschätzbar. Deshalb führen Entwickler statt sinnvollen umfassenden Änderungen lieber kleine lokale Änderungen durch. Dadurch steigt die Redundanz und die Komplexität der Software, was die Qualität und damit die Wartbarkeit weiter verschlechtert. Dieser Effekt der Verschlechterung der Wartbarkeit einer Software über die Zeit wird Software Rotting genannt. ReTest bietet für all diese Probleme eine elegante Lösung: Basierend auf vier Jahren Grundlagenforschung im Bereich Testgenerierung an der Universität des Saarlandes haben wir eine bahnbrechende Technologie entwickelt, um Regressionstests für Benutzeroberflächen von Anwendungen vollautomatisch zu generieren. Solch generierte Regressionstests bergen viele Vorteile und amortisieren sich direkt beim ersten Einsatz: Transparenter Fortschritt: Indem durch häufiges Testen technische Schulden von Änderungen frühzeitig offengelegt werden, wird der tatsächliche Projektfortschritt transparent und Projektleiter behalten die Übersicht. Gleichbleibende Arbeitsbelastung: Frühzeitiges und kontinuierliches Testen vermeidet riskante Änderungen in letzter Minute ebenso, wie die Gefahr von Projektverzug. Gleichbleibend hohe Testqualität: Beim manuellen Testen kommt es oft vor, dass Testfälle und Anwendungsszenarien vergessen oder aus Zeit- und Kostenerwägungen weggelassen werden was die Softwarequalität gefährdet. 1 Tricentis customer survey 2008 "1
Automatische Tests können immer vollständig ausgeführt werden. Da das Programm bei der Generierung der Tests analysiert wird (White Box Testing), sind diese Tests in der Regel sogar vollständiger (größere Testabdeckung) als manuelle Tests (insbesondere beim destruktiven Testen). Mehr Zuversicht: Die Regressionstests können in den Entwicklungsprozess integriert und regelmäßig vollautomatisch ausgeführt werden. Damit haben Entwickler nach durchgeführten Änderungen schnell die Gewissheit, dass sie keine Fehler in das Programm eingeführt haben. Wenn Entwickler Änderungen zuversichtlich durchführen können, können sie notwendige Refaktorings durchführen und so die Codequalität erhöhen bzw. erhalten und Software Rotting entgegenwirken. Vollständige Prüfung: Die Anzahl der Prüfregeln geht keine Kompromisse mehr ein. Die Prüfregeln werden wie die Tests selbst automatisch erstellt und prüfen immer 100% des dargestellten Zustandes ohne dass sie gepflegt werden müssen. Kosteneffizient und schnell: Es müssen nur noch die Teile der Software manuell getestet werden, die auch geändert wurden. Je kürzer die Releasezyklen sind, desto schneller geht das und desto größer ist die Kostenersparnis. Je nach Anwendungsfall besteht das Potential einer Einsparung von über 60% der Testkosten und einer noch größeren Zeitersparnis. Aufbau von ReTest ReTest ist der Name des Gesamtproduktes. Es untergliedert sich in die folgenden Komponenten: - ReCapture: Zum Aufzeichnen von Tests. - RePlay: Zum Abspielen der Tests. - ReBase: Zum automatischen Aktualisieren der Tests. - Surili: Zum vollautomatischen Generieren von Tests. Surili ist die englische Bezeichnung der auf der Insel Java lebenden Affen 2. Abb. 1: Java-Langur (eng. Javan Surili) 2 http://en.wikipedia.org/wiki/javan_surili "2
Funktionsweise von ReTest ReCapture und ReBase Herkömmliche Capture und Replay-Technologien sind aus drei Gründen fragil: 1. Weil sich die Komponenten auf der Oberfläche ändern und dann vom Test nicht mehr erkannt werden. 2. Weil sich der Sollzustand der Anwendung ändert und damit die Prüfregeln (Assertions) ungültig werden. 3. Weil sich das Verhalten der Anwendung ändert und damit das aufgezeichnete Verhalten ungültig wird. Abb. 2: ReCapture GUI ReTest bietet auch eine Capture-Komponente an (genannt ReCapture). Im Gegensatz zu den meisten Capture und Replay- Werkzeugen speichert ReTest beim Aufzeichnen eines Tests zu jeder Oberflächenkomponente möglichst viele unterschiedliche Identifikationsmerkmale: - den Pfad zur Komponente im logischen Aufbau der Oberfläche - je nach GUI-Technologie Id, Name und Class-Attribute - Beschriftung, Icon und/oder Label Abb. 3: Identifikationsmerkmale - die Pixelkoordinaten der Komponente In Verbindung mit der ReTest-Komponente zum automatischen Aktualisieren der Tests (genannt ReBase) entfällt der Pflegeaufwand fast vollständig. Denn wenn sich ein oder mehrere dieser Attribute ändern, so kann die Komponente über die verbleibenden unveränderten Attribute weiterhin eindeutig identifiziert werden. Wenn viele oder alle Attribute geändert werden sollen, so empfiehlt es sich dies Schrittweise zu tun und jeweils zwischen den einzelnen Schritten die Tests automatisch zu aktualisieren. Auf diese Art können auch umfangreichere Änderungen am Design der Oberfläche durchgeführt werden, ohne das die Tests manuell angepasst oder neu aufgezeichnet werden müssen. Die Festlegung der Prüfregeln (d.h. der Festlegung was richtig und was falsch ist) stellt immer eine Gradwanderung dar: zu viele Prüfregeln bedeuten mehr Pflegeaufwand bei korrekten Änderungen, bei zu wenigen Prüfregeln werden möglicherweise Fehler nicht erkannt. ReTest speichert den Sollzustand der Anwendung separat vom Testablauf ab. Wenn sich nun der Sollzustand der Anwendung ändert, so kann der neue Sollzustand der Anwendung erneut aufgezeichnet werden. Die Tests werden somit aktualisiert. Solange sich nicht das Verhalten der Anwendung in Bezug auf den Ablauf eines Tests ändert, JTextField username = (JTextField) getuicomponent( getactivewindow(), "JPanel_0/JPanel_0/JTextField_0"); assertequals(null, username.getaction()); assertequals(-1, username.getbackground().getrgb()); assertequals(true, username.isenabled()); assertequals(true, username.isfocusable()); assertequals("lucida Grande", username.getfont().getname()); assertequals("login", username.getname()); assertequals(13, username.getfont().getsize()); assertequals(0, username.getfont().getstyle()); assertequals(28, username.getheight()); assertequals("", username.gettext()); assertequals(254, username.getwidth()); assertequals(71, username.getx()); assertequals(1, username.gety()); Abb. 4: manuell erstellte Prüfregeln, äquivalent zu ReTest kann dies vollautomatisch geschehen. Damit werden immer alle maximal möglichen Prüfregeln automatisch erstellt und automatisch aktualisiert es entsteht kein manueller Aufwand beim Aufzeichnen oder bei der Pflege der Tests. "3
RePlay und ReBase Normalerweise wird die Funktionalität einer Anwendung bei der Abnahme getestet und dokumentiert. Danach wird die Funktionalität durch Regressionstests immer wieder getestet, um sicher zu stellen, dass bereits getestete Funktionalität durch Änderungen nicht unbeabsichtigt beeinträchtigt wird. Regressionstests sind damit eigentlich ein Werkzeug der Änderungskontrolle in Bezug auf das Verhalten der Anwendung. Die graphische Darstellung der Benutzeroberfläche erschließt sich nicht direkt aus dem Quellcode und entsteht erst zur Laufzeit, gehört damit also auch zum Verhalten der Software. Beim Quellcode nutzt man Versionsverwaltungssysteme um Änderungen zu kontrollieren und den aktuell gültigen Stand einer Software zu dokumentieren. Änderungen gegen diesen gültigen Stand werden mittels eines Diff minimiert dargestellt. Bei der Kontrolle von durchgeführten Änderungen bspw. in Reviews werden in der Regel nur die Diffs betrachtet. Eigentlich sind Regressionstests das falsche Werkzeug für das gewünschte Ziel, wodurch der enorme Pflegeaufwand entsteht. Eigentlich möchte man statt einem funktionalen Test vielmehr etwas wie ein behavioral Diff eine Auflistung aller Änderungen des Verhaltens der Software (inklusive der Darstellung) im Vergleich zur letzten korrekten Version. Genau wie bei einem Diff aus dem Versionsverwaltungssystem kann man so effizient ungewollte Änderungen erkennen. Hat man alle ungewünschten Änderungen im Verhalten der Software beseitigt, so wird der neue Stand zur neuen korrekten Version. Abb. 5: Beispiel eines Behavioral Diff ReTest realisiert genau dieses Behavioral Diff. Dadurch müssen beim Aufzeichnen eines Tests auch keine Prüfregeln (Assertions) festgelegt werden. Man nutzt die Anwendung einfach ganz normal, wie ein Endbenutzer dies tun würde. ReTest dokumentiert hier den kompletten Zustand aller Fenster der Anwendung inklusive Screenshots bei jedem einzelnen Ausführungsschritt ab. Nun kann die Anwendung geändert werden. Die ReTest-Komponente RePlay führt nun die aufgezeichneten Tests aus und stellt dabei alle Änderungen jeder einzelnen Komponente fest und berichtet diese. Unbeabsichtigte Änderungen können so schnell und einfach erkannt werden. Sind diese unbeabsichtigten Änderungen behoben, so werden die Tests mittels der ReTest-Komponente ReBase aktualisiert und damit der neue Stand der Software zum aktuellen Stand. Die Tests prüfen dabei nicht auf fachliche Korrektheit, sondern auf Wiederholgenauigkeit nach Änderungen am Programm. "4
Surili Monkey-Testing bedeutet ein Programm mittels zufälliger Benutzereingaben (also zufällige Klicks auf Buttons, Links, Menü-Einträge und zufällige Eingaben auf der Tastatur) auf Robustheit zu testen (also nach Programmabstürzen zu suchen). Dies ist der Ausgangspunkt von ReTest. Da ReTest sich beim Testen in die Anwendung einklinkt (White-Box-Testing) ist Surili auch hier schon wesentlich effizienter als herkömmliche Ansätze. In der Abb. 6 sieht man ein Eingabefeld für ein Geburtsdatum. Ein menschlicher Tester kann gar nicht wissen, ob dieses Feld wie vorgesehen einfach in der Datenbank gespeichert wird, oder ob der Wert des Feldes Auswirkungen auf den weiteren Programmablauf hat. Surili kann unterscheiden, welche Benutzereingaben eine Auswirkung auf die Businesslogik haben und sich auf diese konzentrieren. Abb. 6: Eingabefeld Geburtsdatum Indem es sich Algorithmen der künstlichen Intelligenz bedient, ist Surili effektiver als eine rein zufällige Aneinanderreihung von Benutzereingaben: Es verwendet einen genetischen Algorithmus um die Tests zu verfeinern und die Testabdeckung (Coverage) der Anwendung zu erhöhen. Es beginnt mit den genannten zufälligen Tests oder verwendet existierende aufgezeichnete Tests. Wie schon erwähnt sammelt ReTest beim Ausführen der Tests Feedback aus der Anwendung ein. Dazu benötigt es lediglich den Byte Code wie er z.b. an Kunden ausgeliefert wird. Basierend auf diesem Feedback werden die Tests iterativ bewertet und verbessert um die Testabdeckung immer weiter zu erhöhen. In der Praxis haben wir dabei bis zu 90% Zweigabdeckung (Branch Coverage) durch die generierten Tests erreicht bei minimalem menschlichen Aufwand. Abb. 7: Funktionsweise von ReTest Ein weiterer Vorteil des genetischen Algorithmus besteht darin, das die Optimierung der Tests jederzeit abgebrochen und wiederaufgenommen werden kann. Sie endet wenn die Testabdeckung nicht weiter erhöht werden kann. Bei der Generierung werden die Tests in einem headless Modus generiert, also ohne tatsächliche graphische Darstellung der Oberfläche, was den Prozess enorm beschleunigt. Erst nach der Generierung werden die Tests über die ReBase- Komponente mit Screenshots und den Zustandsdaten der Ausführung angereichert. Wenn bestimmte Bereiche einer Anwendung durch Surili nicht erreicht werden, z.b. weil sie passwortgeschützt sind oder weil der notwenige Anwendungsfall besonders komplex ist, dann können einfach einzelne Tests aufgezeichnet werden, die "5
Surili als Vorlage dienen. Anhand des beobachteten Verhaltens kann der Affe lernen wie diese Bereiche der Anwendung erreicht werden können, um dort dann weitere Tests durchzuführen. So kann beispielsweise der Happy Path eines Anwendungsfalles, also der ideale Standardfall aufgezeichnet werden. Alle Abweichungen und Ausnahmen (die Sad Paths ) kann der Affe dann selbstständig ausprobieren. Menschliche Tester haben zudem häufig Probleme destruktiv zu testen. Auf diese Art kann der Affe aus einem aufgezeichneten Test z.b. 50 weitere Tests vollautomatisch ableiten. Wenn sich das aufgezeichnete Verhalten der Anwendung korrekterweise ändert müssten alle entsprechenden Tests normalerweise angepasst werden. Mit Surili besteht die Möglichkeit die Tests automatisch anzupassen. Zukünftige Weiterentwicklungen Derzeit ist ReTest für die Benutzeroberflächentechnologie Swing implementiert und kann mittels Ant und Maven direkt in den normalen Qualitätssicherungsprozess integriert werden. ReTest ist sehr modular aufgebaut. Da die zugrunde liegenden Benutzungsparadigmen immer die Gleichen sind, funktioniert ReTest für quasi alle Benutzeroberflächen wie Web, SWT, JavaFX und weitere. Es ist geplant zukünftig weitere Oberflächentechnologien zu unterstützen. Ebenso sind weitere Szenarien zur Integration von ReTest in die vorhandenen Qualitätssicherungsprozesse geplant. "6
Besuchen Sie uns auf www.retest.de oder schreiben Sie uns an info@retest.de. Wir freuen uns auf Sie! fully automated progress testing software engeneering effiency ReTest Dr. Jeremias Rößler In der Technologiefabrik A309 Haid- und- Neu- Str. 7 76131 Karlsruhe 0721/72380106 info@retest.de www.retest.de Whitepaper Version 2.1