Testen ist ein leidiges Thema: kaum jemand schreibt sie gerne, jeder weiß das sie notwendig sind und alle freuen sich wenn es welche gibt. Dabei ist es auch in einer Java EE Umgebung gar nicht schwer die Grundlage fürs Testen zu legen.
Arquillian
Arquillian ist ein Test-Framework das es uns erlaubt die Anwendung innerhalb des Containers zu testen. Anders als „einfache“ Junit Tests laufen also auch unsere Testfälle innerhalb unseres ApplicationServers. Das hat den entscheiden Vorteil das alle Infrastruktur-Dienste auch in unseren Tests zur Verfügung stehen: Datenbankzugriffe, Transaktionshandling und CDI Injection? Kein Problem! Dabei ist Arquillian nicht an Junit gebunden, sondern bietet auch Unterstützung für TestNG. Da in unserem Beispiel Junit zum Einsatz kommt sieht unser Maven pom.xml wie folgt aus:
<!--Junit als Basis für unsere Tests--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- Arquillian JUnit Integration --> <dependency> <groupId>org.jboss.arquillian.junit</groupId> <artifactId>arquillian-junit-container</artifactId> <scope>test</scope> </dependency> <!-- Verbindung zum ApplicationServer --> <dependency> <groupId>org.wildfly.arquillian</groupId> <artifactId>wildfly-arquillian-container-managed</artifactId> <version>1.1.0.Final</version> <scope>test</scope> </dependency>
Neben Junit selbst und die zugehörige Integration mit Arquillian deklarieren wir noch einen Konnektor der dafür Sorge trägt wie unsere Tests später ihren Weg auf den ApplicationServer finden. Diese Konnektoren existieren für diverse Application Server in aller Regel in 3 unterschiedlichen Ausführung
- embedded, Arquillian startet einen eigenen ApplicationServer mit festgelegter Konfiguration
- remote, Arquillian deployt die Anwendung auf einen entfernt laufenden Server
- managed, Arquillan deployt die Anwendung auf einen lokal bereits laufenden Server
Bevor unsere Tests nun ausgeführt werden, muss nun dafür gesorgt werden das Arquillian die Anwendung für unsere Tests bereit stellt. Der erste Schritt dazu ist unseren Test mit „@RunWith(Arquillian.class)“ zu annotieren damit Arquillian sich dieser Tests annimmt. Nun benötigen wir das eigentliche Deployment unserer Anwendung. Dazu setzt Arquillian auf „ShrinkWrap“ um dynamische Archive zu bauen auf dessen Basis die Tests statt finden. Damit haben wir die Möglichkeit nur dediziert Teile zu deployen die für unseren aktuellen Test benötigt werden. Dabei ist allerdings darauf zu achten das auch wirklich alle abhängigen Klassen zusätzlich hinzufügt werden und auch die Deskriptoren müssen enthalten sein. Eine charmante Variante dagegen ist es das bereits gebaute Archiv unserer Anwendung zu verwenden und anschließend nur noch die nötigen Manipulationen durchzuführen (z.B. CDI Alternativen aktivieren oder die Datasource zu ändern). Die Vorbereitung eines solchen Archivs geschieht in einer Methode die mittels „@Deployment()“ annotiert ist.
@Deployment() public static WebArchive createDeployment() { WebArchive deployment = ShrinkWrap .create(WebArchive.class, "junit-demo-test.war") .as(ZipImporter.class) .importFrom(new File("target/junit-demo.war")) .as(WebArchive.class) .addPackage("de.gedoplan.webclients.test") deployment.delete("META-INF/persistence.xml"); deployment.addAsResource("test-persistence.xml", "META-INF/persistence.xml"); return deployment; }
Es bietet sich an eine solche Methode in eine Basisklassen aus zu lagern, sodass unser konkreter Test wie folgt aussieht:
@RunWith(Arquillian.class) public class CustomerServiceTest extends TestBaseClass { @Inject CustomerService service; @Test public void calculateCustomerDiscount() { Assert.assertTrue(service.calculateCustomerDiscount("ALFAA").getDiscount() == 2.5); } }
Hier ist gut zu sehen wie einfach dann ein konkreter Testfall ist. Da unser Test auf dem Server abläuft können wir CDI nutzen um direkt unsere Services zu injizieren und unsere Tests zu schreiben.
Alles auf Anfang, DBUnit
Eine Herausforderung bei den oben gesehenen Tests sind die Daten. Da wir mit einem echten Datenbestand arbeiten auf den wir auch schreibend zugreifen ist es anzuraten einen festen Testdatenbestand bereit zu stellen. Diese Daten sollten nicht nur für konkrete Tests verändert werden können, sondern sollten vor allem Konstant bei jedem Test in gleicher Art und Weise zur Verfügung steht. Hier auf die „normale“ Testdatenbank zu setzen wird in aller Regel dazu führen das regelmäßig Tests aufgrund von geänderter Testdaten Fehlschlagen. DBUnit ist dabei ein etablierter Kandidat der auf eine XML-Datenbeschreibung setzt um Datenbanken in einen festgelegten Zustand zu versetzen. Eine solche Struktur beschreibt dann die konkreten Tabellen (shippers, categories…) und die Werte der entsprechenden Tabellenspalten (ShipperID, CategoryName…)
<dataset> <shippers ShipperID="1" CompanyName="Speedy Express" Phone="(503) 555-9831"/> <shippers ShipperID="2" CompanyName="United Package" Phone="(503) 555-3199"/> <shippers ShipperID="3" CompanyName="Federal Shipping" Phone="(503) 555-9931"/> <categories CategoryID="1" CategoryName="Beverages" Description="Soft drinks, coffees, teas, beers, and ales" Picture="beverages.gif"/> <categories CategoryID="2" CategoryName="Condiments" Description="Sweet and savory sauces, relishes, spreads, and seasonings" Picture="condiments.gif"/> <categories CategoryID="3" CategoryName="Confections" Description="Desserts, candies, and sweet breads" Picture="confections.gif"/> </dataset>
Einen solchen Datenbestand können wir dann zu Beginn jeden Testes bereitstellen
@Before public void initData() throws Exception { //Verbindung aufbauen Class driverClass = Class.forName("org.h2.Driver"); Connection jdbcConnection = DriverManager.getConnection("jdbc:h2:~/junit;AUTO_SERVER=TRUE", "sa", "sa"); //Datenladen InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("dbunit_full.xml"); IDataSet dataset = new FlatXmlDataSet(inputStream); //Datenbank füllen DatabaseOperation.CLEAN_INSERT.execute(jdbcConnection, dataset); }
Im obigen Beispiel verwenden wir aus gutem Grund eine separate H2-Datenbank. Mittels „CLEAN_INSERT“ veranlassen wir DBUnit dazu alle bestehenden Daten zu löschen. Das ist die beste Grundlage für valide Tests, sollte allerdings nicht mit der „echten“ Testdatenbank durchgeführt werden (! dazu passend müssen wir natürlich auch die test-persistence.xml anpassen die wir oben für unser Deployment erzeugen und mit ShrinkWrap installieren)
Wunde Finger? Testdaten mit Jailer
Die oben verwendeten Testdaten müssen natürlich nicht von Hand geschrieben werden. DBUnit bietet hierfür eine sehr einfache API an um bestehenden Datenbanktabellen in ein solches Format zu übertragen (DatabaseExportSample.java). Hier können wir festlegen ob eine komplette Datenbank exportiert oder selektiv nur benötige Tabelle ausgewertet werden sollen. Eine noch schönere Variante ist die Verwendung eines zusätzlichen Tools: „Jailer“. Jailer bietet uns die Möglichkeit sehr Feingranulat die Daten aus zu wählen, welche für wir exportieren wollen. Gerade bei sehr großen Datenbeständen will man als ersten Stand in aller Regel nur eine überschaubare Anzahl von Testdaten haben. Dafür bietet Jailer entsprechende Selektionskriterien und ermittelt selbstständig die benötigten Relationen (die bei Bedarf auch individuell deaktiviert werden können). Im unteren Beispiel selektieren wir z.B. ausgehenden von einer geringen Anzahl an Bestellungen alle abhängigen Daten, unterbinden aber bestimmte Relationen, wie z.B. customers > orders um die Datenmenge gering zu halten.
Wie wir gesehen haben sind Tests im Java EE Umfeld kein Hexenwerk. DBUnit, Jailer, Arquillian und Junit bietet optimale Voraussetzungen um Tests zu schreiben. Allerdings bleibt es dabei: Schreiben müssen wir die Tests weiterhin selber.