JBOSS SEAM – Test Driven Development mit TestNG

Samstag, 26. Februar 2011 von  
unter Fachartikel Architektur

Das systematische und automatisierte Testen von Anwendungen ist heutzutage unverzichtbarer Bestandteil moderner Software-Entwicklungsmethoden und hat als testgetriebene Entwicklung (TDD)/testgetriebenes Design (TDD) bereits zur Qualitätsverbesserung der Software zahlloser Softwareprojekte geführt. Die Entwicklung von Seam geschah von Anfang an mit dem Focus auf die möglichst einfache Anwendbarbarkeit automatisierbarer Testverfahren. 

Dieser Artikel erklärt an einigen Beispielen, wie man unter Einsatz des JBOSS SEAM Frameworks, des TestNG Frameworks und der Seam Gen-Anwendung leicht Unternehmens-Anwendungen generieren und im TDD (Test Driven Development) Applikationen testen und weiterentwickeln kann. Es werden weiterhin Seams Möglichkeiten für Komponenten-und Integrationstest beschrieben. Zielgruppe des Artikels sind Java Entwickler mit JBoss Seam Kenntnissen, die bereits Erfahrungen mit dem Unit Test von Anwendungen und dem TestNG Test Framework haben, oder diese und ähnliche Erfahrung ergänzen möchten.
Für die Generierung einer Unternehmens-Anwendungen mittels JBOSS SEAM und eine erste Einführung in die einzelnen Komponenten eines JBoss Seam Enterprise Projekts wird der bereits vorhandene Artikel in diesem Blog empfohlen [link: http://www.binaris-informatik.de/?p=327]. 

Hier nun die Erklärungen zum Einsatz der erzeugten Klassen für die TestNG Tests auf der Grundlage eines z.B. mit dem SEAM-eigenen Generator Seam Gen erzeugten Seam BeispielProjects, welches durch das Anlegen und Erweitern der benötigten Klassen zum Testen vervollständigt wird.

Beispielprojekt

Der Einstieg in die Arbeit mit JBOSS SEAM und dem TDD mit TestNG wird in diesem Artikel anhand einiger annotierter Test-Klassen veranschaulicht. Die Beispiele sind einfache Fragmente zur Erzeugung einer voll TDD gesteuerten Unternehmens Anwendung, welche auf dem JBoss Application Server läuft. Begonnen wird mit einem einfachen Komponententest zum Einstieg, danach folgen Integrationstests sowohl für die Persistenz-und Datenbankschicht als auch fürs User Interface.

Für das Erstellen des Beispielprojekts wurden die folgenden Pakete und Komponenten verwendet: Als flexible Entwicklungsumgebung Eclipse 3.6 Helios in der JEE 64bit Version, als Application Server der JBOSS in Version 5.1. Um mit Eclipse eine komfortable Steuerung des JBOSS AS zu ermöglichen, wurden die JBOSS Tools als Plugin installiert. Für das Beispielprojekt wurde die SEAM Version 2.2.0 verwendet, die unter der bekannten Web-Adresse [link: http://seamframework.org/Seam2/Downloads ] zum Download bereit steht. Der Download des TestNG Frameworks erfolgt am besten von der Seite http://www.testng.org und entsprechend der Download von JUnit von http://www.junit.org .

Erste Schritte

Ein Projekt als Beispielapplikation kann schnell gemäß den SEAM Vorgaben angelegt werden. Hierfür wird die Anleitung in diesem Blog empfohlen [link: http://www.binaris-informatik.de/?p=327]. Nach erfolgreichem Anlegen per Seam Gen erfolgt das Einfügen des Projektes in eclipse. Innerhalb des Workspace befindet sich nun also ein konfiguriertes und ausführbares Projekt. Der Einsatz des Test Frameworks TestNG im Rahmen des TDD auf dem JBOSS SEAM und die weitere Implementierung der Test Klassen und deren Annotierungen für die Beispiel Applikationen werden im Folgenden an einfachen Beispielen erläutert:

TESTEN VON SEAM ANWENDUNGEN – GRUNDLAGEN UND TESTKLASSEN

Seam Komponenten werden als annotierte POJOs implementiert und sind somit per Unit-Tests auf ihre erwartete Funktionsweise hin überprüfbar. Für komplexe Anwendungen reichen Tests auf Basis eines Unit-Testings jedoch nicht aus. Die äußerst wichtigen integrativen Tests einer Web Components Anwendung werden in ihrer Bedeutung für die Anwendung bereits erkennbar beim simplen manuellen oder automatisierten Durchlaufen (z.B. mit WebTest) der einzelnen Formular Workflows z.B. bei einem ad hoc Entwicklertest oder aber durch die QA Beauftragten. Deshalb bietet SEAM die Testbarkeit von Seam Anwendungen als wichtigen Bestandteil von JBOSS SEAM. Dadurch wird das Schreiben von Tests mit TestNG stark erleichtert, die eine komplette Benutzer Interaktion im Zusammenspiel aller Software Systemkomponenten simulieren. Die geschriebenen Tests können direkt innerhalb der Eclipse IDE oder durch Ausführung eines automatisierten build Skripts (z.B. Ant, Groovy) auch unabhängig von einer Entwicklungsumgebung ausgeführt werden.

Die Basics:

Unter einem Komponententest (Unit-Test) versteht man den Test einer einzelnen Komponente eines Software Systems. Im Falle von Seam sind dies typischerweise Action/Command Handler oder andere Geschaftslogik implementierende Klassen. Um den Begriff einer Komponente konkret erfassen zu können, werden POJOs als zu testende Komponenten festgelegt. Sehr einfache POJOs können dann ohne Bezug zu anderen POJOs getestet werden. Bei komplizierteren und realistischen POJOs ist das eher nicht mehr der Fall. Dann werden die Mocks benötigt und generiert, welche für den Test die zu simulierenden Relationen zu den wirklichen Komponenten implementieren. Seam stellt die Klasse SeamTest im Package org.jboss.seam.mock bereit, um dieses Mocking zu unterstützen.

Ein sehr weitverbreitetes System für den Komponententest im Java-Bereich ist JUnit von Erich Gamma, dessen Integration in eclipse optimal realisiert ist. Die Entwickler von Seam haben zwar gegen JUnit aber für TestNG entschieden, für das auch Eclipse PlugIns existieren. Die Verwendung von TestNG auf Basis von Ant-Targets ermöglicht aber auch IDE unabhängiges Testen.  Wenn man TestNG Tests innerhalb von eclipse per Eclipse TestNG PlugIn ausführen möchte, müssen die folgenden .jar Dateien und außerdem das  /bootstrap  Verzeichnis zum Anfang des classpath hinzugefügt werden:

/lib/test/jboss-embedded-all.jar

/lib/test/hibernate-all.jar

/lib/test/thirdparty-all.jar

/lib/jboss-embedded-api.jar

/lib/jboss-deployers-client-spi.jar

/lib/jboss-deployers-core-spi.jar

Um TestsNG Tests zum BeispielProject hinzuzufügen, muß ein TestNG xml Descriptor neben dem Test Klassen Verzeichnis angelegt werden.  Der TestNG xml Descriptor muß hierfür einen Namen der Form  *Test.xml haben.

Durch den Einsatz von JBoss Embedded im Unit und Integration Testing, muß der VM noch das Start Argument  -Dsun.lang.ClassLoader.allowArraySyntax=true mitgegeben werden, wenn man von der Verwendung der JRE 6 Update 24 als neustem Sicherheits Update und höherer Versionen ausgeht.

Ein einfacher SEAM TestNG Test:

Es wird mit einem einfachen Seam Test begonnen und dafür eine simple Klasse namens CommandHandler definiert, deren Verhalten getestet werden soll:

@Stateless

public class CommandHandler implements CommandHandlerLocal {

    public String simpleTest() {

         return „SimpleTest“;

    }

}

Die Klasse CommandHandler besitzt eine einzige parameterlose Methode der Signatur public String simpleTest(), die bei Aufruf den String „SimpleTest“ zurückgibt. Die Testklasse namens CommandHandlerTest testet in ihrer Test Methode namens testSimpleTest diese Methode, indem sie die zu erwartende Zeichenkette mit dem Rückgabewert des Methodenaufrufs der Methode simpleTest() vergleicht:

public class CommandHandlerTest extends SeamTest {

    @Test

    public void testSimpleTest() {

        CommandHandlerLocal aCmdHandler = new CommandHandler();

        assert aCmdHandler.simpleTest().equals(„SimpleTest“);

    }

}

Der Klassenname der von SeamTest abgeleiteten Klasse setzt sich dabei aus dem Namen der zu testenden Klasse und dem Suffix „Test“ zusammen. Dies ist eine weiterempfehlenswerte Namens Konvention und wird z.B. auch von Seam Gen so verwendet. Weiterhin erkennt man die Annotation @Test. Alle Methoden einer Klasse, die als Testmethoden ausgeführt werden sollen, sind hiermit zu annotieren. @Test ist eine TestNG Annotation und sagt dem TestNG System, diese Methode auszuführen und das Testergebnis im HTML-Format zu präsentieren. Der Bedeutung der aufgerufenen Testmethode ist schnell erklärt. Im Fall eines negativen Testergebnisses wirft der assert Aufruf eine Exception, bei einem positiven Ergebnis nicht. Die generierte HTML Seite mit den Testergebnisse zeigt in der Farbe Grün positiv verlaufene Tests, in der Farbe Rot die negativ verlaufenen Tests.

Ein Test kann logischerweise auch aus mehreren Klassen bestehen, wobei alle mit @Test annotierten Methoden ausgeführt werden. Alle Tests dieses Abschnitts sind im BeispielProject enthalten. Das Ant build File dieses Projekts enthält das target=“test“, welches den Aufruf von TestNG per  run ant test  Aufruf startet.

Der Titel für den HTML Output stammt dabei aus der TestNG-Konfigurationsdatei für die Test Suite, die nun erklärt wird.

Der SEAM Test Suite:

<!DOCTYPE suite SYSTEM „http://beust.com/testng/testng-1.0.dtd“ >

<suite verbose=“2″ parallel=“false“ />

    <test>

        <classes>

            <classname=“de.beispielProject.test.CommandHandlerTest“/>

        </classes>

    </test>

</suite>

TestNG faßt Tests in einer Suite mit dem Element<suite> zusammen. Die einzelnen Tests werden mit dem Element<test> definiert und bekommen einen frei wählbaren Namen, der im HTML Output für die Testergebnisse erkennbar ist.

Das Zusammenspiel von Testmethoden und Seam Komponenten:

Zur Simulation der Komponentenverwaltung von Seam unter Verwendung der Bijection wird als zu testende Komponente die Command Handler Klasse MyCommandHandler verwendet, die das MyCommand Interface implementieren soll. Das Attribut customer wird sowohl mit @In als auch mit @Out annotiert:

@In

@Out

private Customer customer;

Eine Testmethode, die über die Simulation der Bijection auf das Attribut customer zugreift, ist im Folgenden dargestellt:

@Test

public void testMyCommandHandler() throws Exception {

    MyCommand myCommandHandler = new MyCommandHandler();

    Customer customerIn = new Customer(„Chuck“, „Norris“, ’m’);

    this.setField(myCommandHandler, „customer“, customerIn);

    Customer customerOut =

             (Customer) this.getField(myCommandHandler, „customer“);

    assert customerOut.getFamilyname().equals(„Norris“);

}

Hierzu wird die Methode setField() der Klasse SeamTest verwendet, die auch Zugriff auf private deklarierte Attribute hat, wie z.B. das Attribut customer. Über die Methode getField() wird dasselbe Attribut danach gelesen und mit einer assert-Anweisung überprüft, ob beide Nachnamen übereinstimmen, sowohl der geschriebene als auch der gelesene Nachname des Customers.

Zugriff auf die Test-Datenbank:

Die bisherigen Tests konnten die prinzipielle Funktionsweise des SEAM Test-Framework zeigen. Auf diese Art und Weise können Tests für weitere beliebige Komponenten entwickelt und durchgeführt werden. Bekanntlich benötigen IT Systeme aber eine Persistenzschicht mit einer entsprechenden Datenbank zur Bereitstellung wichtiger Business Daten und der Möglichkeit erforderliche C-R-U-D Operationen (Create, Read, Update, Delete) auszuführen. Hier nun eine kurze Einführung in das Testen von Seam Anwendungen, die einen JPA EntityManager verwenden (siehe hierzu auch den Blog Eintrag in diesem Blog „JBOSS SEAM: EJB3 u. Hibernate für die UnternehmensDB“). Als Test wird das Speichern eines Customers in der Datenbank und das Lesen dieses Customers aus der Datenbank definiert. Der Beispielcode für den Test könnte folgendermaßen aussehen:

public class DatenbankTest extends SeamTest {

    EntityManager em() {

        EntityManagerFactory emf = Persistence.

                     createEntityManagerFactory(„BeispielProject“); 

        EntityManager em = emf.createEntityManager();

        assert em.isOpen();

        return em;

    }

    @Test

    public void testCustomer() throws Exception {

        Customer customer = new Customer(„Chuck“,“Norris“, ‚m‘);

        EntityManager em = em();

        em.getTransaction().begin();

        em.persist(customer);

        em.getTransaction().commit();

        // customer saved, now read customer:

        // no ’select‘ displayed at log output,

        // since customer is read from Hibernate’s first level cache

        customer = em.find(Customer.class, customer.getId());

        assert customer.getFamilyname().equals(„Norris“);

    }

}

Der Test erzeugt ein Customer Objekt, welches persistent gemacht wird. Dafür wird ein Entity-Manager über die Methode em() erzeugt. Die dafür verwendete deklarative Persistenzeinheit heißt „BeispielProject“. Der in der EJB3 Spezifikation definierte Standardfall einer SessionBean, beschreibt, alle Methoden innerhalb einer Transaktion aufzurufen, weshalb der schreibende Aufruf von em.persist(customer) die Transaktionsklammer ( begin(), commit() ) verwendet.

Die Testmethode testCustomer() muß genauso vorgehen. Der Aufruf von persist() erfolgt ebenso in einer Transaktionsklammer ( begin(), commit() ). Dann wird der durch diesen Aufruf in die Datenbank geschriebene Customer mit der Methode find() wieder ausgelesen und als eigentlicher Test der Methode die Gleichheit des geschriebenen und des gelesenen Nachnamens des Customers geprüft. Hibernate realisiert als JPA Implementierung mit der vom Entity-Managers erhältlichen Hibernate Session einen First-Level-Cache, wodurch an dieser Stelle kein neuer lesender Datenbankzugriff erforderlich ist.

Das BeispielProject verwendet eine import-test.sql-Datei, um für den Test Setup der Anwendung initiale Datensätze in die Datenbank einzufügen. So wird z.B. der Customer „Chuck Norris“ mit dem entsprechenden Primary Key in die Datenbank eingefügt. Der beschriebene Test kann dies überprüfen, indem auf das Einfügen eines neuen Customers verzichtet wird und direkt der über import-test.sql eingespielte Customer ausgelesen und überprüft wird. Dabei werden die per Seam Gen erzeugten Konfigurationsdateien persistence-test.xml und import-test.sql als Test-Konfigurationen verwendet werden, um TestNG Tests gegen die DB laufen zu lassen. Hierzu werden jeweils das DB Schema und die Testdaten gemäß Konfiguration in der persistence-test.xml gedroppt, dann das DB Schema und die Testdaten aus der import-test.sql immer neu in die Datenbank importiert, bevor die Tests ausgeführt werden.

User Interface Tests und JSF Lifecycle Methoden:

Nun zum Test der Benutzer-Schnittstelle bzw. der Interaktion des Benutzers und der korrekten Reaktionen der Anwendung auf diese Interaktion. Bei dieser Art von Integrationstests wird der Test auf echten Seam Objekten durchgeführt, statt den Test auf extra für Unit-Tests erzeugten Objekte durchzuführen.

Das wichtigste Mock-Element für den Test der Anwender Oberfläche und die Anbindung dieser Oberfläche an Command Handler ist die in SeamTest eingebettete Klasse FacesRequest. Die JSF-Spezifikation definiert folgende Phasen zur Bearbeitung eines HTTP JSF-Requests, wenn man diese ohne Verwendung des „immediate“ Attributs mit dem Wert true in den entsprechenden Eingabekomponenten bzw. Steuerkomponenten betrachtet (mit immediate=“true“ könnte man sofortige Validierung durchführen bzw. sofortige Applikationslogik ausführen und die Response nach der anschließenden Event Bearbeitung sofort rendern):

1. Wiederherstellung des Komponentenbaums: restoreView()

2. Übernahme der Anfragewerte/Request Parameter auslesen: applyRequestValues()

3. Validierung durchführen: processValidations()

4. Modell aktualisieren: updateModelValues()

5. Anwendungslogik ausführen: invokeApplication()

6. Rendern derAntwort: renderResponse()

Für eine detaillierte Beschreibung der JavaServer-Faces Lifecycle Methoden und der einzelnen  Request-Response Phasen wird auf eins der bekannten genannten JSF-Bücher verwiesen.

Die fünf Methoden der letzten fünf Phasen 2-6 sind in der Klasse FacesRequest bzw. einer Basis Klasse als Methoden ohne Inhalt definiert und können in den Test Klassen überschrieben werden. Als zu testende Oberfläche wird die Login-Seite des BeispielProjects verwendet (siehe hierzu auch den Artikel „JBOSS SEAM: Unternehmens-Login Komponenten“ in diesem Blog). Der verwendete Command Handler ist die Komponente authenticator des Typs Authenticator mit der Methode authenticate(). Die implizite Verbindung der Benutzeroberfläche mit dem Handler erfolgt über die Seam-Komponente identity. Der zu entwickelnde Test simuliert über Aufruf der Setter der Identity Instance die Benutzereingaben und ruft dann die Methode authenticate() des Command Handlers auf. Sowohl der positive als auch der negative Fall einer Anmeldung bzw. eines Login-Versuchs können so getestet werden.

Hier also die Klasse AuthenticationTest, die den Anmeldeversuch des Customers „Chuck Norris“ simuliert:

public class AuthenticationTest extends SeamTest {

@Test

public void authenticate () throws Exception {

    new FacesRequest () {

        AuthenticatorLocal authenticator;

        @Override

        protected void updateModelValues () {

            Identity.instance().setUsername(„Chuck Norris“);

            Identity.instance().setPassword(„action“);

            authenticator = (AuthenticatorLocal) getInstance(„authenticator“);

        }

        @Override

        protected void invokeApplication () {

            assert authenticator.authenticate(); 

        }

    }.run();

}

Dazu werden in der inneren anonymen Klasse FacesRequest die Methode updateModelValues() und invokeApplication() überschrieben und der Request ausgeführt. Dann werden der Benutzername und das Passwort gesetzt. Es erfolgt über die SeamTest-Methode getInstance() ein Zugriff auf eine Seam- Komponente. Diese wurde nicht von der Test Klasse erstellt, sondern ist eine vom Seam-Laufzeitsystem erstellte Komponente, siehe hierzu auch den Blog Beitrag „JBOSS SEAM: Unternehmens-Login Komponenten“ in diesem Blog. Der eigentliche Test erfolgt in der Methode invokeApplication(), worin die Methode authenticate() aufgerufen wird. Die Methode authenticate() zeigt nicht nur durch ihr boolesches Ergebnis den Status der Authentifizierung an, sondern injiziert den durch die Authentication gefundenen Customer als Komponente in den Session Scope der Anwendung. Auch dieses Verhalten kann sehr einfach getestet werden. Dieser Test muß in der RenderPhase der Request Bearbeitung erfolgen, weshalb der Test um die weitere überschreibende Methode renderResponse() erweitert wird:

@Override

protected void renderResponse() {

    // Using the @Out annotation within handler

    Customer customer = (Customer) lookup(„customer“);

    assert customer.getFamilyname().equals(„Norris“);

}

Der für die (Username, Password)-Kombination („Chuck Norris„, „action„) authentifizierte Benutzer in der Datei import-test.sql ist „Chuck Norris“, weshalb im Test auf seinen Nachnamen geprüft wird. Auch hier wurde die Komponente customer nicht durch die Testklasse, sondern durch das Seam-Laufzeitsystem erstellt. Die Methode lookup() ist eine Alternative zu der verwendeten Methode getInstance(). Die Methode getInstance() erzeugt die gesuchte Instanz, falls sie noch nicht vorhanden ist, während die Methode lookup() in diesem Fall den Wert null zurückliefert.

Zu guter letzt soll die Oberfläche für eine explizite Falscheingabe getestet werden. Bei einer fehlgeschlagenen Authentication wird eine Fehlermeldung generiert, die in das Attribut currentMessages der Instanz der Seam Klasse FacesMessages geschrieben wird. Per Aufruf des entsprechenden Getters getCurrentMessages() können diese Fehlermeldungen in Form einer Liste von FacesMessages dann an JSF weitergegeben werden. Um diese Fehlermeldung im Test zu generieren, muß eine nicht vorhandene und damit falsche Kombination aus (Username, Password) eingegeben werden. Die Test Klasse AuthenticateErrorTest sieht dann folgendermaßen aus:

public class AuthenticateErrorTest extends SeamTest {

@Test

public void authentiWithError() throws Exception {

    new FacesRequest() {

        AuthenticatorLocal authenticator;

        @Override

        protected void updateModelValues () {

            Identity.instance().setUsername(„Chuck Norris“);

            Identity.instance().setPassword(„action1“);

            authenticator = (AuthenticatorLocal) getInstance(„authenticator“);

        }

        @Override

        protected void invokeApplication() {

            assert !authenticator.authenticate();

        }

        @Override

        protected void renderResponse() {

            boolean found = false;

            List<FacesMessage> messages = FacesMessages.instance().getCurrentMessages();

            for (FacesMessage message : messages) {

                if („Wrong customername/password combination“.equals(

                    message.getDetail())) {

                    found = true;

                    break;

                }

            }

            assert found;

        }

    }.run();

}

Es wird das falsche Passwort verwendet und somit ein negatives Authentikationsergebnis simuliert. Der eigentliche Test auf die korrekte Fehlermeldung ist in der Methode renderResponse() realisiert. Da die Instanz der Fehlermeldungen alle Fehlermeldungen der JSF-Seite als Liste enthält, kann diese Liste zum Durchsuchen nach der entsprechenden Fehlermeldung einfach per for-each Schleife (read-only) durchlaufen werden.

Vereinfachungen des Test-Codes durch Einsatz der JSF EL und deren SEAM Erweiterungen:

Die von Seam eingeführten Erweiterungen der JSF EL sind wirklich sehr nützlich, denn durch die Verwendung von Seams EL in den Testklassen kann eine Vereinfachung des Codes und eine Verringerung der loCs erreicht werden. Hier der erneute Test auf positive Authentication und die Überprüfung des Nachnamens des eingeloggten Customers, diesmal unter Verwendung der Expression Language (EL):

public class AuthenticateCustomerELTest extends SeamTest {

@Test

public void Customer() throws Exception {

    new FacesRequest() {

        @Override

        protected void applyRequestValues() {

            setValue(„#{identity.username}“,“Chuck Norris“);

            setValue(„#{identity.password}“,“action“);

        }

        @Override

        protected void invokeApplication() {

            assertBoolean (

                invokeMethod(„#{authenticator.authenticate}“));

        }

        @Override

        protected void renderResponse() {

            assert (getValue(„#{customer.familyname}“).equals(„Norris“) )

        }

    }.run();

}

Weiterhin kann z.B. geprüft werden, ob der aktuelle Request innerhalb einer longrunning Conversation (Aufruf der Methode isLongRunningConversation()) erfolgt ist, oder auf die Benutzertransaktion zugegriffen wird (Aufruf der Methode getUserTransaction()).

Konfiguration der TestNG Tests mittels components.xml:

Die Konfiguration der Tests erfolgt entsprechend der Deployment-Konfiguration. Einzige Ausnahme hierbei ist die Datei components.xml, die minimal anzupassen ist. Der Applikationsnamen Prefix beim JNDI-Namens-Pattern ist hierbei zu entfernen, und es muß Seam mitgeteilt werden, den embedded EJB3 Container zu verwenden (siehe hierzu auch den Artikel „JBOSS SEAM: EJB3 u. Hibernate für die Unternehmens-DB“ in diesem Blog):

<components..[…].>

<core:init jndi-pattern = „#{ejbName}/local debug = „true“/>

<core:ejbinstalled=“true“/>

[…]

</components>

Die Konfiguration des BeispielProject verwendet eine MySQL Datenbank innerhalb des JBoss Applicationservers.

Diese ist für die Tests mit Hilfe einer angepaßten persistence-test.xml-Datei verfügbar, die auf die MySQL DataSource des JBoss verweist und beim Testlauf diese automatisch starten kann, wenn sie noch nicht gestartet ist. Das Verzeichnis /test-mock des Projekts BeispielProject enthält diese beiden Dateien. Das Ant-Build-File verwendet diese automagisch, weshalb keine Änderungen vorzunehmen sind.

Hier abschließend noch ein ausführlicher Test der Klasse UserAccount des BeispielProjects. Die Test Klasse UserAccountTest simuliert mit ihren Testmethoden das Login eines bekannten Users, die Registrierung eines neuen Users, die Änderung des Benutzernamen bzw. die Änderung des Passworts eines existierenden Users:

package de.beispielProject.test;

import java.util.Date;

import javax.persistence.EntityManager;

import javax.persistence.EntityManagerFactory;

import javax.persistence.Persistence;

import org.jboss.seam.mock.SeamTest;

import org.testng.annotations.Test;

public class UserAccountTest extends SeamTest {

    @Test

    public void testLogin() throws Exception {

        new FacesRequest() {

            @Override

            protected void processValidations() throws Exception {

            }                

            @Override

            protected void updateModelValues() throws Exception {

                assert getValue(„#{identity.loggedIn}“).equals(false);

                setValue(„#{identity.username}“, „Chuck Norris“);

                setValue(„#{identity.password}“, „action“);

            }

            @Override

            protected void invokeApplication() {

                invokeMethod(„#{identity.login}“);

            }

            @Override

            protected void renderResponse() {

                assert getValue(„#{user.email}“).equals(„chuck.norris@googlemail.com“);

                assert getValue(„#{user.username}“).equals(„Chuck Norris“);

                assert getValue(„#{user.password}“).equals(„action“);

                assert getValue(„#{identity.loggedIn}“).equals(true);

            }

        }.run();

    }

    @Test

    public void testRegistration() throws Exception {

        new FacesRequest() {

                 

            @Override

            protected void processValidations() throws Exception {    

                validateValue(„#{user.username}“, „userchooser“);

                validateValue(„#{user.password}“, „verysecret“);

                validateValue(„#{user.email}“, „userchooser@web.de“);

                assert !isValidationFailure();

            }                 

            @Override

            protected void updateModelValues() throws Exception {     

                setValue(„#{user.username}“, „userchooser“);

                setValue(„#{user.email}“, „userchooser@web.de“);

                setValue(„#{user.password}“, „verysecret“);

                setValue(„#{user.verify}“, „verysecret“);       

            }                 

            @Override

            protected void invokeApplication() {

                assert invokeMethod(„#{userAccount.registerUser()}“).equals(„registered“);

            }                 

            @Override

            protected void renderResponse() {

                String username = (String) getValue(„#{identity.username}“);

                assert username.equals(„userchooser“);

            }

        }.run();

    }

    @Test

    public void testChangePassword() throws Exception {

        new FacesRequest() {

            @Override

            protected void processValidations() throws Exception {

            }                 

            @Override

            protected void updateModelValues() throws Exception {

                setValue(„#{identity.username}“, „Chuck Norris“);

                setValue(„#{identity.password}“, „action“);

                invokeMethod(„#{identity.login}“);                

                setValue(„#{userAccount.oldPassword}“, „action“);

                setValue(„#{userAccount.newPassword}“, „newAction“);

                setValue(„#{userAccount.newPasswordVerify}“, „newAction“);

            }              

            @Override

            protected void invokeApplication() {

                invokeMethod(„#{userAccount.changePassword()}“);

            }               

            @Override

            protected void renderResponse() {

                String userPassword = (String) getValue(„#{user.password}“);   

                assert userPassword.equals(„newAction“);

            }

        }.run();

    }

    @Test

    public void testChangeUserName() throws Exception {

        new FacesRequest() {

            @Override

            protected void processValidations() throws Exception {

            }

            @Override

            protected void updateModelValues() throws Exception {

                setValue(„#{identity.username}“, „Chuck Norris“);

                setValue(„#{identity.password}“, „newAction“);

                invokeMethod(„#{identity.login}“);                 

                setValue(„#{user.username}“, „Chucky Norris“);

            }

            @Override

            protected void invokeApplication() {

                invokeMethod(„#{userAccount.updateUser()}“);

            }

            @Override

            protected void renderResponse() {

                String userName = (String) getValue(„#{user.username}“);

                assert userName.equals(„Chucky Norris“);

                String idUsername = (String) getValue(„#{identity.username}“);

                assert idUsername.equals(„Chucky Norris“);

            }

        }.run();

    } 

    //@Test, without the Test annotation this test() won’t run!

    public void test() throws Exception {

        new FacesRequest() {

            @Override

            protected void processValidations() throws Exception {

            }

            @Override

            protected void updateModelValues() throws Exception {

            }

            @Override

            protected void invokeApplication() {

            }

            @Override

            protected void renderResponse() { 

            }

        }.run();

    }

    EntityManager em() {

        EntityManagerFactory emf = Persistence

                     .createEntityManagerFactory(„BeispielProject“);

        EntityManager em = emf.createEntityManager();

        assert em.isOpen();

        return em;

    }

} 

Der Einsatz anderer Test Frameworks mit SeamTest:

Wie beschrieben bietet Seam TestNG Unterstützung out-of-the-box, kann aber genauso gut ein anderes Test Framework verwenden, wie z.B. JUnit. Hierfür muß die Test Basis-Klasse zur Bereitstellung der Test Infrastruktur einfach die in der Klasse AbstractSeamTest deklarierten Methoden implementieren, welche von der eigentlichen Test Klasse dann entsprechend aufzurufen sind:

super.begin() vor jeder test Methode

super.end() nach jeder test Methode

super.setupClass() für den Setup der Integrations-Test Umgebung.

      Diese Methode sollte aufgerufen werden, bevor irgendeine Test-Methode aufgerufen wird.

super.cleanupClass() um die Integrations-Test Umgebung wieder

      in den initialen Zustand vor dem ersten Test zurückzuversetzen.

super.startSeam() um Seam beim Start der integrativen Tests zu starten.

super.stopSeam() um Seam nach dem letzten der integrativen Tests zu stoppen. 

Für weiterführende Informationen über die Einrichtung wiederverwendbarer und gleichzeit Klassen spezifischer Test Setups mit Hilfe fachlicher Testdaten aus Setup Dateien unter Verwendung des Template Method Patterns wird das empfehlenswerte Buch „TEST-DRIVEN DEVELOPMENT By Example“ von Kent Beck empfohlen.

Weiterführende Informationen über JSF und die zu testenden Phasen zur Bearbeitung eines JSF-Requests können z.B. dem hier verwendeten Buch „JavaServerFaces 2.0“ von Martin Marinschek, Michael Kurz und Gerd Müllan entnommen werden, welches sehr zu empfehlen ist. Zum Thema TestNG Einsatz kann das sehr empfehlenswerte Buch „JBOSS SEAM Die Web-Beans-Implementierung“ von Bernd Müller verwendet werden, welches auch papierlos erhältlich ist.

Dieser Artikel hat als Quelle Beispielcodes aus den SEAM Examples und Inhalte aus der offiziellen, frei verfügbaren JBOSS SEAM Dokumentation mit dem Titel “Seam – Contextual Components, A Framework for Enterprise Java 2.2.0.GA“ von den Autoren Gavin King, Christian Bauer et al als auch eigene Erfahrungen mit dem JBOSS SEAM Framework zur Grundlage, sowie zusätzliche Informationen aus den genannten Büchern.

Kommentare

4 Kommentare zu “JBOSS SEAM – Test Driven Development mit TestNG”

  1. Von Services – REST, Hibernate, JSF mit JBoss 7 : binaris informatik GmbH am Sonntag, 27. Oktober 2013 15:14

    […] positiv hinsichtlich des Themas Test Driven Development (TDD) mit Java und speziell zum TDD mit TestNG und JBoss Seam fällt noch auf, dass hier gleich entsprechende Test-Projekte mitangelegt werden […]

  2. Von Services – mit Hibernate 4, Vaadin 6, JBoss 7 : binaris informatik GmbH am Samstag, 1. März 2014 11:18

    […] TDD für den JBoss mit TestNG gibt es auch bereits einen Blog-Eintrag hier. An dieser Stelle darf auf die sehr empfehlenswerten „Test-Driven Development mit Java“ […]

  3. Von Architekturen – mit Spring CDI, JPA, JBoss, JavaEE : binaris informatik GmbH am Montag, 30. Juni 2014 21:40

    […] gibt es ebenfalls viele interessante Diskussionen und Beispiele sowie Vergleiche dazu. Zum Thema “TDD mit TestNG“ gibt es hier in diesem Blog auch einen […]

  4. Von Services – TDD mit Vaadin Testbench, Java, Hibernate, JBoss : Softwareentwicklung, Projektmanagement & Schulung | binaris informatik GmbH am Sonntag, 26. Juli 2015 18:52

    […] Seam RichFaces-Applikationen mittels Test-Framework TestNG gibt es ebenfalls einen Blog-Eintrag hier. Wie sieht es nun mit dem Erstellen und Ausführen von Integrations-Tests für mittels […]

Einen Kommentar hinzufügen...

Sie müssen registriert und angemeldet sein um einen Kommentar zu schreiben.