Testen Sie Webanwendungen mit HttpUnit

In einer typischen Unternehmensanwendung müssen in vielen Bereichen Tests durchgeführt werden. Ausgehend von den einfachsten Komponenten, Klassen müssen Entwickler oder spezialisierte Testentwickler Komponententests programmieren, um sicherzustellen, dass sich die kleinsten Einheiten der Anwendung korrekt verhalten. Jede Komponente kann möglicherweise die Komponententests alleine bestehen. Entwickler müssen jedoch sicherstellen, dass sie wie erwartet zusammenarbeiten - als Teil eines Subsystems und als Teil der gesamten Anwendung -, daher müssen Integrationstests durchgeführt werden. In einigen Projekten müssen die Leistungsanforderungen erfüllt sein, sodass die Qualitätssicherungsingenieure Belastungstests durchführen , um die Leistung der Anwendung unter verschiedenen Bedingungen zu überprüfen und zu dokumentieren. Während der Anwendungsentwicklung führen Qualitätssicherungsingenieure automatisierte und manuelle Funktionstests durchum das Verhalten der Anwendung aus Sicht des Benutzers zu testen. Wenn ein Entwicklungsprojekt einen bestimmten Meilenstein fast erreicht hat, können Abnahmetests durchgeführt werden, um zu überprüfen, ob die Anwendung die Anforderungen erfüllt.

HttpUnit ist ein auf JUnit basierendes Framework, das die Implementierung automatisierter Testskripte für Webanwendungen ermöglicht. Es eignet sich am besten für die Durchführung automatisierter Funktionstests oder Abnahmetests. Wie der Name schon sagt, kann es für Unit-Tests verwendet werden. Typische Web-Layer-Komponenten wie JSP-Seiten (JavaServer Pages), Servlets und andere Vorlagenkomponenten eignen sich jedoch nicht für Unit-Tests. Verschiedene Framework-basierte MVC-Komponenten (Model-View Controller) eignen sich besser zum Testen mit anderen Test-Frameworks. Struts-Aktionen können mit StrutsUnit Unit-getestet werden, und WebWork 2-Aktionen können beispielsweise ohne Webcontainer Unit-getestet werden.

Testziele

Bevor wir uns mit den Details der Architektur und Implementierung befassen, ist es wichtig, genau zu klären, was die Testskripte für die Webanwendung benötigen. Es ist möglich, das Verhalten eines gelegentlichen Website-Besuchers zu simulieren, indem Sie einfach auf interessante Links klicken und Seiten in zufälliger Reihenfolge lesen. Das Ergebnis dieser zufälligen Skripte würde jedoch nicht die Vollständigkeit und Qualität der Anwendung beschreiben.

Eine typische Webanwendung für Unternehmen (oder eine komplexe Website) enthält mehrere Dokumente, in denen die Anforderungen der verschiedenen Benutzer oder Anwendungsbetreuer beschrieben werden. Dies können Anwendungsfallspezifikationen, nicht funktionierende Anforderungsspezifikationen, Testfallspezifikationen, die von den anderen Artefakten abgeleitet wurden, Designdokumente für Benutzeroberflächen, Modelle, Akteurprofile und verschiedene zusätzliche Artefakte umfassen. Für eine einfache Anwendung könnte die gesamte Spezifikation möglicherweise aus einer einfachen Textdatei mit einer Liste von Anforderungen bestehen.

Aus diesen Dokumenten müssen wir eine organisierte Liste von Testfällen erstellen. Jeder Testfall beschreibt ein Szenario, das ein Webbesucher über einen Webbrowser ausführen kann. Es empfiehlt sich, Szenarien mit ähnlicher Größe anzustreben. Größere Szenarien können in kleinere Teile zerlegt werden. In vielen ausgezeichneten Büchern und Artikeln wird die Erstellung von Testfallspezifikationen erörtert. Nehmen wir für diesen Artikel an, Sie haben eine Reihe von Elementen, die Sie für Ihre Webanwendung testen möchten, die in Gruppen von Testfallszenarien organisiert sind.

Zeit zum Herunterladen!

Okay, jetzt kennen wir das langweilige Zeug, lass uns ein paar coole Spielsachen herunterladen! Zunächst benötigen wir ein installiertes Java 2 SDK, um unsere Tests zu kompilieren und auszuführen. Dann müssen wir das HttpUnit-Framework herunterladen - derzeit in Version 1.5.5. Das Binärpaket enthält alle erforderlichen Bibliotheken von Drittanbietern. Wir benötigen außerdem das Ant-Build-Tool, um die Tests auszuführen und Berichte automatisch zu generieren. Jede relativ neue Version dieser Tools würde wahrscheinlich funktionieren. Ich bevorzuge es einfach, die neueste und beste Version von allem zu verwenden.

Zum Schreiben und Ausführen von Tests empfehle ich die Verwendung einer IDE mit einem eingebetteten JUnit-Testläufer. Ich verwende Eclipse 3.0M7, um meine Testskripte zu entwickeln, aber IntelliJ unterstützt ebenso JUnit wie die zuletzt veröffentlichten IDEs.

HttpUnit: Der HTTP-Client-Simulator

Da wir Webanwendungen testen möchten, sollte sich das Testtool im Idealfall genau wie die Webbrowser der Benutzer verhalten. Unsere Anwendung (das Testziel) sollte keinen Unterschied bemerken, wenn Seiten für einen Webbrowser oder das Testtool bereitgestellt werden. Genau das bietet HttpUnit: Es simuliert die GET- und POST-Anforderungen eines normalen Browsers und bietet ein schönes Objektmodell, mit dem unsere Tests codiert werden können.

Weitere Informationen zu den übrigen Klassen und Methoden finden Sie im detaillierten API-Handbuch. Abbildung 1 gibt nur einen kurzen Überblick über die Klassen, die ich am häufigsten benutze. Eine Benutzersitzung (eine Folge von Interaktionen mit der Webanwendung) ist mit a gekapselt WebConversation. Wir konstruieren WebRequests, konfigurieren normalerweise die URL und die Parameter und senden sie dann über die WebConversation. Das Framework gibt dann a zurück WebResponse, das die vom Server zurückgegebene Seite und Attribute enthält.

Hier ist ein Beispiel für einen HttpUnit-Testfall aus den HttpUnit-Dokumenten:

/ ** * Überprüft, ob das Senden des Anmeldeformulars mit dem Namen "master" * zu einer Seite führt, die den Text "Top Secret" enthält. WebRequest request = new GetMethodWebRequest ("//www.meterware.com/servlet/TopSecret"); WebResponse-Antwort = Konversation.getResponse (Anfrage); WebForm loginForm = response.getForms () [0]; request = loginForm.getRequest (); request.setParameter ("name", "master"); response = gespräch.getResponse (Anfrage); assertTrue ("Login nicht akzeptiert", response.getText (). indexOf ("Du hast es geschafft!")! = -1); assertEquals ("Seitentitel", "Top Secret", response.getTitle ()); }}

Architektonische Überlegungen

Beachten Sie, dass das obige Java-Beispiel den Domänennamen des Servers enthält, auf dem die Anwendung ausgeführt wird. Während der Entwicklung eines neuen Systems befindet sich die Anwendung auf mehreren Servern, und auf den Servern können mehrere Versionen ausgeführt werden. Offensichtlich ist es eine schlechte Idee, den Servernamen in der Java-Implementierung beizubehalten. Für jeden neuen Server müssen wir unsere Quellen neu kompilieren. Andere Elemente sollten nicht in den Quelldateien enthalten sein, z. B. Benutzernamen und Kennwörter, die für die jeweilige Bereitstellung konfigurierbar sein sollten . Auf der anderen Seite sollten wir eine einfache Testfallimplementierung nicht überarchitekturieren. Normalerweise enthält die Testfallspezifikation bereits die meisten Systemstatus- und spezifischen Parameterbeschreibungen für unser Szenario, sodass es keinen Sinn macht, alles in der Implementierung parametrierbar zu machen.

Während des Codierens werden Sie feststellen, dass viele Codeabschnitte in mehr als einer Testfallimplementierung angezeigt werden (möglicherweise in allen Testfällen). Wenn Sie ein erfahrener objektorientierter Entwickler sind, werden Sie versucht sein, Klassenhierarchien und allgemeine Klassen zu erstellen. In einigen Fällen ist dies sehr sinnvoll. Beispielsweise sollte das Anmeldeverfahren eine gemeinsame Methode sein, die für alle Testfälle verfügbar ist. Sie müssen jedoch einen Schritt zurücktreten und feststellen, dass Sie kein neues Produktionssystem auf der Testzielanwendung aufbauen. Diese Java-Klassen sind lediglich Testskripte zur Validierung der Website-Ausgabe. Üben Sie den gesunden Menschenverstand aus und streben Sie einfache, sequentielle und in sich geschlossene Testskripte an.

Die Testfälle sind typischerweise fragil. Wenn ein Entwickler eine URL ändert, werden die Layouts neu organisiert

wird einfaches, sequentielles Skript

Die Rückverfolgbarkeit ist für unsere Testfälle von entscheidender Bedeutung. Wenn etwas in KA-BOOM geht oder beispielsweise ein Berechnungsergebnis falsch ist, ist es wichtig, den Entwickler auf die entsprechende Testfallspezifikation und die Anwendungsfallspezifikation zu verweisen, um eine schnelle Fehlerbehebung zu erreichen. Kommentieren Sie daher Ihre Implementierung mit Verweisen auf die ursprünglichen Spezifikationsdokumente. Das Einfügen der Versionsnummer dieser Dokumente ist ebenfalls nützlich. Dies kann nur ein einfacher Codekommentar oder ein komplexer Mechanismus sein, bei dem die Testberichte selbst mit den Dokumenten verknüpft sind. Wichtig ist, dass die Referenz im Code enthalten ist und die Rückverfolgbarkeit erhalten bleibt .

Wann darf ich Code schreiben?

Nachdem Sie die Anforderungen (Anwendungsfalldokumente und entsprechende Testfallspezifikationen) kennen, die Grundlagen des Frameworks verstehen und eine Reihe von Architekturrichtlinien haben, können Sie mit der Arbeit beginnen.

Für die Entwicklung der Testfallimplementierungen arbeite ich lieber in Eclipse. Erstens hat es einen schönen JUnit-Testläufer. Sie können eine Java-Klasse auswählen und im Menü Ausführen als JUnit-Komponententest ausführen. Der Läufer zeigt die Liste der erkannten Testmethoden und das Ausführungsergebnis an. Wenn während des Testlaufs alles in Ordnung ist, wird eine schöne grüne Linie angezeigt. Wenn eine Ausnahme oder ein Assertionsfehler aufgetreten ist, wird eine quälende rote Linie angezeigt. Ich denke, das visuelle Feedback ist wirklich wichtig - es bietet ein Gefühl der Leistung, insbesondere beim Schreiben von Komponententests für Ihren eigenen Code. Ich verwende Eclipse auch gerne für seine Refactoring-Funktionen. Wenn ich feststelle, dass ich innerhalb einer Testfallklasse Codeabschnitte kopieren und einfügen muss, kann ich stattdessen einfach das Refactoring-Menü verwenden, um eine Methode aus dem Codeabschnitt zu erstellen.Wenn ich feststelle, dass zahlreiche Testfälle dieselbe Methode verwenden, kann ich das Menü über das Menü in meine Basisklasse aufrufen.

Based on the architectural requirements above, for each project, I typically create a base test-case class, which extends the JUnit TestCase class. I call it ConfigurableTestCase. Each test-case implementation extends this class, see Figure 2.

ConfigurableTestCase typically contains the common methods and initialization code for the test case. I use a property file to store the server name, the application context, various login names for each role, and some additional settings.

The specific test-case implementations contain one test method per test-case scenario (from the test-case specification document). Each method typically logs in with a specific role and then executes the interaction with the Web application. Most test cases do not need a specific user to accomplish the activities; they typically require a user in a specific role, like Administrator, or Visitor, or Registered User. I always create a LoginMode enum, which contains the available roles. I use the Jakarta Commons ValuedEnum package to create enums for the roles. When a specific test method in a test-case implementation logs in, it must specify which login role is required for that particular test scenario. Of course, the ability to log in with a specific user should also be possible, for example, to verify the Registered User use case.

After each request and response cycle, we typically need to verify if the returned page contains an error, and we need to verify our assertions about what content the response should contain. We must be careful here as well; we should only verify items that are not variable and not too fragile in the application. For example, if we assert specific page titles, our tests will probably not run if the language is selectable in the application and we want to verify a different language deployment. Similarly, there's little point in checking an item on the page based on its position within a table layout; table-based designs change frequently, so we should strive to identify elements based on their IDs. In case some important elements on the page don't have IDs or names, we should just ask the developers to add them, rather than trying to work around them.

JUnit assertions offer a poor approach for checking if the look and feel, layout, and page design comply with the requirements. It is possible, given an infinite amount of time for the test development, but a good human tester can assess these things more efficiently. So concentrate on verifying the Web application's functionality, rather than checking everything possible on the page.

Here's an updated test scenario based on our test-case architecture. The class extends ConfigurableTestCase, and the login details are handled in the base class:

 /** * Verifies that submitting the login form with the name "master" results * in a page containing the text "Top Secret" **/ public void testGoodLogin() throws Exception { WebConversation conversation = new WebConversation(); WebResponse response = login(conversation, LoginMode.ADMIN_MODE); assertTrue( "Login not accepted", response.getText().indexOf( "You made it!" ) != -1 ); assertEquals( "Page title", "Top Secret", response.getTitle() ); } 

Tips and tricks

Most scenarios can be handled quite easily by setting WebForm parameters and then looking for specific elements with results in the WebResponse pages, but there are always some challenging test cases.





Wenn der Besucher die ID eines Formularelements strukturiert oder ändert, wird er wahrscheinlich keinen Unterschied feststellen, aber Ihre Testskripte werden ausgeblasen. Erwarten Sie für jede Testfallimplementierung viel Nacharbeit und Änderungen. Objektorientiertes Design könnte den Aufwand für die Überarbeitung gemeinsamer Teile in den Testfällen verringern. Aus der Sicht eines Qualitätssicherungsingenieurs oder Testers bin ich jedoch sicher, dass eine Interaktion mit einer Website einfacher zu warten und zu reparieren ist. #####