Blackbox, White Box oder Acryl Box
Wie kommt es, dass umfangreiche Unit- und Integrationstests für die "unteren" Schichten einer JSF-basierten Enterprise-Anwendung als das Normalste der Welt angesehen werden, das Testen der UI dank "eXtreme Clicking" aber nach wie vor einem Glückspiel gleicht?
Ein wesentlicher Grund hierfür ist in der Tatsache zu sehen, dass sich im UI-Umfeld vorwiegend Blackbox-Testverfahren etabliert haben. Bei einem Black-box Test wird ein Request mittels headless-Browser (z. B. HTTPUnit) gegen die laufende Anwendung geschickt und das Resultat – also die HTML-Seite – auf bestimmte, erwartete Inhalte hin durchsucht. Dieses Verfahren ist zwar relativ einfach, birgt aber zwei wesentliche Nachteile in sich. Zum einen führt jede kosmetische Änderung an dem Design der Webanwendung zu einem geänderten Output, was gleichzeitig bedeutet, dass die Tests angepasst werden müssen. Zum anderen – und dieser Punkt ist wesentlich wichtiger – kann der Test zwar eine Aussage darüber treffen, ob die an den Client gelieferte Web Page den Erwartungen entspricht, Informationen über den aktuellen Zustand des Servers (z. B. JSF-Komponentenbaum) bekommt man aber nicht. Ein korrekter Output auf Seiten des Clients bedeutet also nicht automatisch, dass die Anwendung wirklich korrekt funktioniert.
Eine Alternative stellt das White Box Testing dar. Hier werden die serverseitigen Klassen getestet, ohne die Anwendung in einem Container laufen zu lassen. Möglich ist das, indem die Ablaufumgebung durch Mocks simuliert wird. Auch dieses Verfahren bringt das eine oder andere Problem mit sich. Zum einen ist es schwer, ein realistisches, komplexes Szenario zu simulieren. Zum anderen stellen sich fein granulare Tests in der Regel als sehr aufwendig heraus.
Bedeutet das nun, dass "eXtreme Clicking" doch die richtige Lösung zum Testen von UI-lastigen JSF-Anwendungen zu sein scheint? Mitnichten! JBoss hat mit JSFUnit eine Lösung geschaffen, der scheinbar der Brückenschlag zwischen Blackbox und White Box Testing gelungen ist. In dem JBoss eigenen Selbstbewusstsein wurde auch gleich ein neuer Begriff dazu kreiert: Acryl Box Testing.
JSFUnit
Laut JBoss-Marketing ist JSFUnit ein Testframework, das mithilfe einer eigenen kleinen API das vollständige Integration- und Unit Testing einer JSF-Anwendung erlaubt. Die Tests laufen dabei in einem Container ab, wobei dank JSFUnit API neben dem Zugriff auf den HTML Output eines Requests zusätzlich der gesamte interne Status der zu testenden Anwendung, also alle JSF-relevanten Bestandteile (Managed Beans, EL Expressions, JSF-Komponentenbaum) via FacesContext zur Verfügung stehen.
Die Arbeitsweise von JSFUnit bzw. des zugehörigen APIs funktioniert, wie es der Entwickler von anderen Unit-Test-Frameworks gewohnt ist. Zunächst wird über einen oder mehrere Requests ein zu testender Application-Status hergestellt und dieser im Anschluss über eine Reihe von Assertions geprüft. Im Gegensatz zum klassischen Black-box Test kann neben der zurückgelieferten HTML-Seite aber auch der gesamte, serverseitige JSF Part der Anwendung auf Herz und Nieren überprüft werden. Und das alles in der realen Deployment-Umgebung und mit unterschiedlichen, durch HtmlUnit emulierten Browserversionen. Listing 1 zeigt einen klassischen JSFUnit-Test, wie er sich auch im Getting Started der JSFUnit-Dokumentation wiederfindet.
public class JSFUnitTest extends ServletTestCase {public static Test suite() {return new TestSuite( JSFUnitTest.class );}public void testInitialPage() throws IOException {// HTTP Request sendenJSFSession jsfSession = new JSFSession("/index.faces");// JSFClientSession emuliert den BrowserJSFClientSession client = jsfSession.getJSFClientSession();// JSFServerSession erlaubt Zugriff auf JSF StateJSFServerSession server = jsfSession.getJSFServerSession();// Test auf korrekte NavigationassertEquals("/index.xhtml", server.getCurrentViewID());// Test auf korrekte KomponenteUIComponent prompt = server.findComponent("greeting");assertTrue(prompt.isRendered());// Test auf korrekte Managed BeanassertEquals("Stan", server.getManagedBeanValue("#{foo.text}"));}}
Um den in Listing 1 aufgezeigten Test laufen lassen zu können, ist zusätzlich ein kleiner "Eingriff" in die web.xml und somit eine Veränderung der JSF-Anwendung notwendig. Das ist natürlich nicht nach jedermanns Geschmack. Nutzt man aber ein Ant- oder Maven-basiertes Deployment, ist es problemlos möglich, mit einer speziellen web.xml zu arbeiten, die nur im Fall der Tests herangezogen wird. Da JSFUnit speziell in Hinblick auf das Testen von JSF-Anwendungen konzipiert wurde, unterstützt es von Haus aus alle gängigen JSF-Implementierungen. Angefangen von MyFaces 1.1.x und MyFaces 1.2.x über Sun JSF RI 1.1.x und Sun JSF RI 1.2.x wird seit Kurzem auch Sun JSF RI 2.0.0 Beta2 unterstützt. Es spielt dabei ebenfalls keine Rolle, ob die JSF-Anwendung auf Basis von JSPs oder Facelets implementiert ist. Neben der JSF-Implementierung selbst stellen häufig auch die innerhalb einer Anwendung verwendeten Komponentenbibliotheken ein Problem bei den Tests dar. JSFUnit ist sowohl mit den RichFaces als auch den Apache MyFaces-Tomahwak-Komponenten getestet. Laut JBoss sollten aber auch alle Komponentenbibliotheken funktionieren, die mit HtmlUnit korrekt zusammenarbeiten.
Schneller, höher, weiter ...
Während die bisher beschriebenen JSFUnit-Tests die Korrektheit einer JSF-basierten Anwendung gewährleisten sollen, dient ein weiteres Set von Tests zur Sicherstellung einer ausreichenden Performance. Mithilfe einer Klasse namens "JSFTimer" können Antwortzeiten gemessen und analysiert werden. Dabei ist es sowohl möglich die gesamte für einen Request/Response verbrauchte Zeit zu messen, als auch die der einzelnen JSF-Lifecycle-Phasen. Insbesondere dieses Feature hilft dabei, Schwachpunkte in der Implementierung aufzuspüren. Nutzt man die Performanceanalyse im Zusammenspiel mit Assertions und entsprechenden Schwellwerten, erhält man mit wenig Aufwand einen Warnmechanismus für eine potenzielle Performanceverschlechterung der JSF-Anwendung:
JSFTimer timer = JSFTimer.getTimer(); assertTrue(timer.getPhaseTime(PhaseId.INVOKE_APPLICATION) < 1000);
Einen habe ich noch ...
Neben den bisher beschriebenen Tests bietet JSFUnit noch ein weiteres, kleines Highlight: statische Analysen. Das sind spezielle Tests, die als klassische Unit-Tests durchgeführt werden können, also ohne dass die Anwendung deployt werden muss. Statische Analysen helfen u. a. dabei, die unterschiedlichen JSF-Konfigurationen und JSP- bzw. Facelet-Seiten auf Konsistenz zu prüfen. So kann z. B. getestet werden, ob Managed Beans korrekt deklariert und verwendet werden, ob referenzierte Konverter und Validatoren die richtigen Interfaces implementieren, ob Session oder Application Scope Managed Beans serialisierbar sind oder ob EL-Ausdrücke auflösbar sind.
Fazit
JSFUnit aus dem Hause JBoss ist gezielt als Testframework für JSF-Anwendungen designt und umgesetzt worden. Diesen positiven Aspekt bemerkt man sofort an den vielen kleinen, netten Features, die über klassische Black- oder White-Box-Tests anderer Webtestframeworks hinausgehen. Neben dem eigentlichen Unit- und Integration-Test-API fallen vor allem die statischen Analysen positiv auf, da insbesondere nicht korrekt gesetzte Referenzen innerhalb der Konfigurations- und View Files in der Praxis häufig zu unnötigen Fehlern führen. Ein kleiner aber wichtiger Tipp noch am Rande: Das Testen von JSF-Anwendungen wird durch die Verwendung von Component IDs deutlich vereinfacht.
Matthias Weßendorf arbeitet für die Oracle an einer Server-side-Push-Lösung für ADF Faces. Er ist PMC Chair von Apache MyFaces. Matthias blogt regelmäßig auf http://matthiaswessendorf.wordpress.com (Twitter: @mwessendorf).









