Mittwoch, 23. Mai 2012


Artikel

Mai 2009 | Artikel

SWTBot für automatisierte SWT- und RCP-Tests Fortsetzung, Teil 2

Teil 1   Teil 2   

Page Objects
Im Unterschied zu dem vorherigen Beispiel (Listing 1) werden in der Praxis zum Zugriff auf die Oberfläche spezielle Klassen erstellt, so genannte Page Objects, und diese dann in den Tests verwendet. Das heißt, man wird in den JUnit-Tests normalerweise nicht direkt auf SWTBot zugreifen. Die Page Objects enthalten eine fachliche API für Benutzeraktionen pro "Seite". So wird die Anzahl der duplizierten Codezeilen verringert. Falls sich im UI etwas ändert, muss nur im jeweiligen Page Object eine Anpassung stattfinden. Das Pattern stammt von Google [4] und ist vor allem bei Webclienttests mit Selenium bekannt. Analog dazu können bei JFace die Dialoge, Views, Editoren, Perspektiven, Wizards, Toolbars etc. im übertragenen Sinn als "Seiten" verstanden und dafür Page Objects erstellt werden. Im Listing 3 wird eine Page-Object-Methode gezeigt, die einen neuen Kontakt zum Adressbuch hinzufügt, indem sie die grafische Oberfläche des zu testenden Clients mit SWTBot fernsteuert. Sie wird vom Test aus aufgerufen (Listing 4).

Listing 3
  1. public void addNewContact(String lastName, String firstName,
  2. String businessPhone, String homePhone, String email, String fax) {
  3. MenuPageObject menu = new MenuPageObject();
  4. menu.newContact().click();
  5. bot.textWithLabel("Last Name").setText(lastName);
  6. bot.textWithLabel("First Name").setText(firstName);
  7. bot.textWithLabel("Business Phone").setText(businessPhone);
  8. bot.textWithLabel("Home Phone").setText(homePhone);
  9. bot.textWithLabel("Email").setText(email);
  10. bot.textWithLabel("Fax").setText(fax);
  11. bot.button("OK").click();
  12. }


Listing 4
  1. @Test
  2. public void addContactByUsingPageObjects() {
  3. main = new MainPageObject();
  4. menu = new MenuPageObject();
  5. contact = new ContactPageObject();
  6. assertEquals(0, main.contacts());
  7. assertFalse(menu.editContact().isEnabled());
  8. contact.addNewContact("Schwaebli", "Hans", "", "", "", "");
  9. assertEquals(1, main.contacts());
  10. assertEquals("Hans", main.contactDetail(0, "First Name"));
  11. main.selectContact("Schwaebli");
  12. assertTrue(menu.editContact().isEnabled());
  13. }


Die Page Objects haben auch den Vorteil, dass man dort auf fachlicher Ebene loggen kann (z.B. "Login user 'eddy' with password 'swtbot' ..."). Dadurch sind die Testschritte anhand der Log-Protokolle gut nachvollziehbar und der eigentliche Testcode bleibt lesbar, da er nicht mit Log-Statements "verseucht" wird. Schlägt ein Test fehl, so wird durch eine Analyse des Protokolls der Fehler oft schneller lokalisiert. Noch nützlicher werden die Page Objects, wenn in ihnen implizite Tests durchführt werden. Beispielsweise lässt sich darin nach dem automatisierten Klick auf den O.K.-Knopf prüfen, ob das Fenster anschließend verschwunden ist.

Codeduplizierungen in den Page Objects werden durch Klassen verringert, deren Methoden nur dem Zweck dienen, Widget-Wrapper-Klassen von SWTBot zurückzuliefern. In den Page Objects können dann die Methoden dieser Klassen (Widget Objects) aufgerufen werden, anstatt die SWTBot-Klasse direkt zu verwenden. Diese Informationen werden toolspezifisch von kommerziellen Konkurrenten auf die eine oder andere Weise automatisch erstellt und sind daraufhin z.B. UI-Komponentenbäume, die in XML abgespeichert werden. Jedoch habe ich die Erfahrung gemacht, dass man mit der Pflege dieser automatisch aufgezeichneten Komponenten und der eigenwilligen halbautomatischen Logik mehr Aufwand hat, als sie manuell in Java zu schreiben.

Rich Client Platform
Auf eine ähnliche Weise wie SWT-Clients lässt sich auch das UI von RCP-Anwendungen mit SWTBot testen. Wenn allerdings die JUnit-Tests als JUnit Plug-in-Test gestartet werden, laufen Test und Client in demselben Thread, wodurch es wie bei SWT-Anwendungen zu Blockierungen kommen kann. Ketan Padegaonkar löst das mit einem eigenen Launcher-Plug-in, in dem dafür gesorgt wird, dass der Test nicht in demselben Thread wie das UI läuft. Die RCP-UI-Tests müssen daher als SWTBot-Test gestartet werden. Der Client wird wie beim JUnit Plug-in-Test implizit vor der Ausführung der Tests gestartet. In der Startkonfiguration muss dazu nur der Produktname oder Applikationsname ausgewählt werden. Eine andere Lösung ist, TestCase#runBare() zu überschreiben, um die Tests in einem separaten Thread laufen zu lassen, wodurch man sie dann doch als JUnit Plug-in-Test starten kann.

Automatisierung und Berichte
Nur während des Entwickelns werden die Tests aus Eclipse heraus gestartet. Für die fortwährende Integration müssen die Tests jedoch unbeaufsichtigt in Gang gesetzt werden. Dies kann man sich in den Ant-Skripten des Plug-ins net.sf.swtbot.build abschauen, d.h. herauskopieren und für seine Zwecke anpassen. Es gibt über die Automatisierung von PDE-JUnit-Tests mit Ant auch einen guten Artikel auf der Eclipse-Seite [5]. Falls statt Ant lieber Maven verwenden werden soll, muss mehr Zeit investiert werden. Man sollte sich mit Ant und dem PDE Build gut auskennen, da ein Testfeature gebaut werden muss, das mindestens das Plug-in beinhaltet, in dem sich die Tests befinden. Die Target Platform, in der die Tests laufen, besteht aus:

  • Rich Client Platform
  • Features und Plug-ins der zu testenden Anwendung
  • SWTBot (net.sf.swtbot.finder und gegebenenfalls net.sf.swtbot.eclipse.finder genügen)
  • Plug-ins und Features, die SWTBot braucht
  • Plug-in, das die Tests enthält

Dieser Automatisierungsschritt ginge etwas leichter mit einer SWT-Instrumentierung, bei der SWTBot von einer eigenen VM aus, Zugriff auf die VM des zu testenden Clients hätte. Das wäre jedoch nicht so RCP-konform wie die Automatisierung über die Target Platform. Eigentlich würde statt eines PDE Builds ein Exportieren vom Test-Plug-in genügen. Jedoch konnte das Ant-Skript, das diesen Vorgang automatisiert, bei uns nur innerhalb von Eclipse fehlerfrei ausgeführt werden.

Am einfachsten ist es, wenn die UI-Tests auf dem gleichen Rechner durchgeführt werden, auf dem das Integrations-Build-Tool, z.B. CruiseControl, läuft. Die Tests können auch in einer eigens dafür vorgesehenen VMWare-Instanz laufen, was aber komplizierter zu automatisieren ist. Sehr nützlich ist auch, dass bei einem Build-Fehler die Ursache dafür in der Regel aus dem Report des Integrationstools hervorgeht, ohne zu diesem Zweck Log-Protokolle analysieren zu müssen. Das JUnit-Testergebnis wird in einer XML-Datei ausgegeben, die mit XSLT in eine HTML-Ansicht umgewandelt werden kann. Bei auftretenden Testfehlern kann SWTBot optional Bildschirmfotos erstellen, wodurch es oft einfacher ist, deren Ursache herauszufinden. Zusammen mit den Log-Protokollen ergibt das einen aussagekräftigen Testbericht.

Workarounds
Eine umfangreiche Dokumentation über SWTBot gibt es bisher noch nicht. Dieser Artikel kann als ausbaufähiges Schema dafür herangezogen werden. Bis es ausführliche Tutorials gibt, kann man aus den Tests und Build-Skripten, die SWTBot für sich selbst verwendet, lernen. Auch die Mailing-Liste hilft weiter [6]. Momentan muss man sich darauf einstellen, bei Bugfixes und Features manchmal mitzuhelfen oder ein bisschen Geduld mitzubringen. Der Großteil davon wird jedoch von Ketan Padegaonkar selbst umgesetzt. Der Rekorder zur Aufzeichnung von Benutzerinteraktionen als SWTBot-Java-"Skripten" ist bisher nur rudimentär. Jedoch stellte ich fest, dass ich ihn nicht sonderlich vermisse. Die vermeintliche Nützlichkeit solcher Rekorder wird ohnehin kontrovers diskutiert [7], [8]. Beachten sollte man auch, dass native Dialoge von SWTBot nicht automatisiert werden können. Das ist primär ein Problem von SWT [9]. Jedoch kann man dieser Einschränkung aus dem Weg gehen, wenn z.B. beim nativen File Chooser zusätzlich ein Textfeld auf dem UI platziert wird, in dem der Pfad alternativ erfasst werden kann. Eine andere Möglichkeit ist, im Testmodus die nativen Widgets durch eigene SWT-Widgets zu ersetzen.

Starthilfe
Aufgrund der noch geringen Dokumentation soll an dieser Stelle kurz beschrieben werden, wie man SWTBot am besten in Eclipse installiert. Es gibt prinzipiell zwei Arten: Entweder über den Downloadlink bzw. die Update-Site [10], oder durch Auschecken der Ressourcen aus dem Repository [11]. Aufgrund der längeren Releasezyklen und der Stabilität der HEAD-Versionen, empfehle ich die zweite Variante. Die Version 2.0 Alpha befindet sich zurzeit im Repository-Unterverzeichnis /branches/java-1.5-api-spike. Checken Sie alle Plug-ins von dort in Ihren lokalen Workspace aus, auch org.rcpmail und org.rcpmail.example. Verwenden Sie am besten die JEE-Ausgaben von Eclipse ab der Version 3.3 aufwärts oder eine entsprechende Target Platform davon. Kopieren Sie nach dem Auschecken von SWTBot die Plug-ins aus /net.sf.swtbot.build/externals/plugins/ in das Plug-ins-Verzeichnis, in der sich Ihre Target Platform befindet (PREFERENCES | PLUG-IN DEVELOPMENT | TARGET PLATFORM). Klicken Sie doppelt im Plug-in net.sf.swtbot.eclipse.ui die Datei MANIFEST.MF an. Wählen Sie im daraufhin erscheinenden Editorfenster das Register OVERVIEW und klicken Sie auf den Link EXPORT WIZARD. Selektieren Sie Directory im Destination-Tab und exportieren Sie nun das Plug-in net.sf.swtbot.eclipse.ui in Ihr Eclipse-Verzeichnis. Kopieren Sie dieses Plug-in ebenfalls in das Verzeichnis, in dem sich Ihre Target Platform befindet, falls es nicht mit Ihrem Eclipse-Verzeichnis identisch ist. Nun müssen Sie Eclipse neu starten. Klicken Sie anschließend RELOAD in den Target Platform Preferences. Vergewissern Sie sich, dass alle soeben hinzugefügten Plug-ins dort aufgelistet werden und bestätigen Sie mit O.K.. Nach dem Build-Vorgang sind die Compile-Fehler von SWTBot weg. Außerdem ist nun die zusätzliche Startkonfiguration SWTBot Test verfügbar, die für RCP-Tests benötigt wird.

Um schnell ein erstes Erfolgserlebnis mit SWTBot zu bekommen, startet man am besten die Klasse net.sf.swtbot.widgets.AllTests im Plug-in net.sf.swtbot.finder.test als JUnit-Test. Dadurch erhält man einen guten Eindruck, was mit SWTBot bereits möglich ist und wie performant die Tests sind. Wem dies zu schnell geht, der kann den Ablauf mit dem VM-Argument Dnet.sf.swtbot.playback.delay=250 etwas bremsen. Es gibt auch ein RCP-Testbeispiel, nämlich das Plug-in org.rcpmail.test, das als SWTBot-Test gestartet wird. In der Startkonfiguration muss im Register MAIN das Produkt org.rcpmail.product ausgewählt werden. Sonst wird nicht die Beispielanwendung vor dem Testlauf gestartet, sondern eine neue Instanz von Eclipse.

Fazit
Es ist beeindruckend, wie viel Ketan Padegaonkar mit SWTBot und allem Drumherum bereits auf die Beine gestellt hat. Auch wenn es noch einige Stolpersteine und Restarbeiten gibt: SWTBot-Tests sind produktiv, leicht wartbar und machen Spaß. Wir haben für eine RCP-Anwendung fast alle unserer funktionalen Tests mit SWTBot automatisiert. Ende 2008 werden der Roadmap zufolge sämtliche SWT Widgets unterstützt werden und eine umfassende Dokumentation verfügbar sein [12]. Nicht nur Java-Entwickler aus der Quality Assurance können damit produktiv testen, sondern auch Cliententwickler mit ihren White-Box-Testfällen. Die weiteren geplanten Features verstärken mein Vertrauen: Unterstützung von Skriptsprachen, eSWT, eRCP, Eclipse Forms, Nebula, GEF und vielleicht sogar auch AWT und Swing. Für mich ist SWTBot kein Geheimtipp mehr, sondern der Favorit. Machen Sie den SWTBot-Test!

Zeljko Markovic arbeitet als Quality Engineer bei der Achievo Deutschland AG in Böblingen. Er beschäftigt sich seit vielen Jahren mit der Java-Anwendungsentwicklung, dem Software-Configuration-Management und der Qualitätssicherung.
  1. SWTBot Proposal
  2. Adressbuchanwendung
  3. Brian Goetz und andere: Java Concurrency in Practice, Addison-Wesley Professional 2006
  4. Pattern von Google
  5. Artikel über Automatisierung von PDE-JUnit-Tests mit Ant
  6. SWTBot Support
  7. Testing GUI Applications
  8. Recording vs Coding
  9. Native Dialoge können von SWTBot nicht automatisiert werden
  10. SWTBot Update-Site
  11. Repository
  12. Roadmap

Teil 1   Teil 2   

Kommentare