Next Generation Testing mit TestNG & Co.
Stefan Edlich, Mark Rambow
Next Generation Testing mit TestNG & Co.
schnell + kompakt
Stefan Edlich, Mark Rambow Next Generation Testing mit TestNG schnell + kompakt ISBN-10: 3-939084-02-6 ISBN-13: 978-3-939084-02-0
© 2007 entwickler.press, ein Imprint der Software & Support Verlag GmbH
http://www.entwickler-press.de/ http://www.software-support.biz/ Ihr Kontakt zum Verlag und Lektorat:
[email protected] Bibliografische Information Der Deutschen Bibliothek Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.ddb.de abrufbar.
Korrektorat: Petra Kienle Satz: text & form GbR, Carsten Kienle Umschlaggestaltung: Melanie Hahn Belichtung, Druck und Bindung: M.P. Media-Print Informationstechnologie GmbH, Paderborn. Alle Rechte, auch für Übersetzungen, sind vorbehalten. Reproduktion jeglicher Art (Fotokopie, Nachdruck, Mikrofilm, Erfassung auf elektronischen Datenträgern oder andere Verfahren) nur mit schriftlicher Genehmigung des Verlags. Jegliche Haftung für die Richtigkeit des gesamten Werks kann, trotz sorgfältiger Prüfung durch Autor und Verlag, nicht übernommen werden. Die im Buch genannten Produkte, Warenzeichen und Firmennamen sind in der Regel durch deren Inhaber geschützt.
Inhaltsverzeichnis Kapitel 1: Einleitung
9
Kapitel 2: Java Annotations
11
Kapitel 3: TestNG – eine Einführung 3.1 Schnellstart Installation Testfile & Buildfile Testergebnis Assertions nutzen 3.2 JUnit-Tests verwenden
15 16 16 18 21 23 24
Kapitel 4: XML-Konfigurationen 4.1 Die DTD verstehen 4.2 Suite 4.3 Test 4.4 Classes 4.5 Packages 4.6 Methods 4.7 Groups und Runs 4.8 Parameter
25 29 29 31 32 32 33 34 36
Kapitel 5: TestNG Annotations 5.1 @Test Parameter groups Parameter dependsOnGroups und dependsOnMethods Parameter expectedExceptions Parameter dataProvider Parameter alwaysRun
37 37 38 38 40 42 42
schnell + kompakt
5
Inhaltsverzeichnis
5.2 Weitere Parameter 5.3 Aufrufe vor und nach Tests @BeforeClass und @AfterClass @BeforeMethod und @AfterMethod Suite- und Test-Level @BeforeGroup und @AfterGroup Parameter für *Before* und *After* 5.4 Parameterübergabe in den Test 5.5 DataProvider Feste Argumentenanzahl Iteratoren übergeben Parametrisierte DataProvider 5.6 Test Factories
43 45 46 47 47 49 50 51 52 52 53 54 56
Kapitel 6: Code-Coverage 6.1 Cobertura 6.2 Emma 6.3 Clover
59 63 66 68
Kapitel 7: Automatisierung und Buildtools 7.1 Automatisierte Tests 7.2 Ant 7.3 Maven 7.4 CruiseControl
73 74 76 82 92
Kapitel 8: Verwendung von IDEs 8.1 Eclipse 8.2 IDEA 8.3 NetBeans
97 97 100 102
Kapitel 9: JavaEE testen
105
6
Inhaltsverzeichnis
Kapitel 10: JTiger
109
Kapitel 11: Vergleich der Test-Frameworks 11.1 Fazit
113 116
Stichwortverzeichnis
117
schnell + kompakt
7
KAPITEL 1 Einleitung
Ausgangspunkt dieses Buches war die Tatsache, dass JUnit zwar gut dokumentiert ist, aber mit TestNG ein leistungsfähigeres Werkzeug vorliegt. Zudem ist die Dokumentation in Englisch geschrieben (was nicht jedermanns Sache ist) und sie ist auch nicht so einfach von vorne nach hinten durchzulesen. Die Dokumentation ist wahrscheinlich für Profis angenehm und als HTML-Seite gut zu durchsuchen. Was fehlt, ist aber eine durchgängige Dokumentation, die – wann immer neue Features/Annotations vorgestellt werden – diese auch mit Beispielen erläutert. Es wird daher versucht, einen roten Faden in diesem Buch zu bieten, der jedem Leser einen Schnelleinstieg in die Materie TestNG ermöglicht. Aber natürlich kann dieses kleine Werk nicht so umfangreich sein wie die Online-Dokumentation. Ein „Missing Manual“ ist keinesfalls der Sinn dieses Werks, da Vollständigkeit nur ein umfangreiches Werk oder die Online-Dokumentation bieten kann. Dennoch wagen wir im zweiten Teil den Blick über den Tellerrand hinaus und stellen wichtige Themen, wie andere Testframeworks (JTiger und den Vergleich mit JUnit), vor. Aber auch das Einbinden von TestNG in Test-/Buildtools wie Maven oder
schnell + kompakt
9
1 – Einleitung
Cruise Control, TestNG und J2EE, IDE Integration und vieles mehr soll dazu beitragen, den Horizont stark zu erweitern. Wir freuen uns über Feedback und wünschen viel Spaß bei der Lektüre!
Stefan Edlich (
[email protected]) Mark Rambow (
[email protected])
10
KAPITEL 2 Java Annotations
Die wichtigste Erleichterung beim Schreiben von Tests entstammt den Java 1.5 Annotations, die auch in TestNG intensiv genutzt werden. Daher hier noch einmal eine kurze Auffrischung der Hintergründe zu Annotations. Bis zu Zeiten von Java 1.4 war klar, dass das Erstellen komplexer Dienste auch viel Code und Konfigurationen erfordert. Das Schreiben von EJBs oder Web Services war lange mit einer Vielzahl von Interfaces oder Deskriptoren behaftet, was aufwändig und fehleranfällig war. Es galt daher schon früh, weitere Informationen in die Java-Datei selbst zu verschieben. Dafür blieb zunächst nur Javadoc übrig. Und so entstanden Tools wie XDoclet, die diese Tags auslesen, verarbeiten und daraus Code, Konfigurationsdateien oder Sonstiges generieren. Dieses Problem wurde für Java im JSR 175 adressiert (.NET kennt hier mit Custom Attributes auch schon länger ein fast gleiches Verfahren). In Java 1.5 (Tiger) wurden dann Annotations eingeführt, die es vorher mit @depricated nur spezifisch gab. Dazu gibt es das Tool apt, welches „Annotation-Prozessoren“ (Code, der com.sun.mirror.* nutzt) liest und ausführt. Annotations können
schnell + kompakt
11
2 – Java Annotations
aus *.java- und *.class-Dateien oder sogar zur Laufzeit ausgelesen werden. Annotations werden unter Java als Interfaces deklariert. Daher ist auch für TestNG eine Bibliothek zu importieren, die diese Annotations (wie @Test) bekannt macht. Hier ein Beispiel aus der Sun-Dokumentation [1]: public @interface RequestForEnhancement { int id(); String synopsis(); String engineer() default "[unassigned]"; String date(); default "[unimplemented]"; }
Wie man sieht, enthält die Deklaration Methoden, die die Elemente/Parameter der Annotation darstellen. Die Methoden selbst enthalten keine Parameter und die Rückgabewerte können nur primitive Typen oder String, Class und enum (und Arrays von diesen Typen) sein. Zusätzlich kann man Default-Werte angeben. Der Aufruf im eigenen Code würde dann so erfolgen [1]: @RequestForEnhancement( id = 2868724, synopsis = "Enable time-travel", engineer = "Mr. Peabody", date = "4/1/3007" ) public static void travelThroughTime( Date destination) { ... }
Dies entspricht der Handhabung von Tests in TestNG, wo z.B. ebenfalls als Parameter von @Test Testgruppen angegeben werden können. Aber natürlich können Methoden auch mit Annotations versehen werden, ohne dass der Annotation Elemente über-
12
Java Annotations
geben werden. Dies nennt man dann Marker (so wie ein Interface bisher selbst auch ein Marker sein konnte), weil es die Methode einfach nur markiert: @RequestForEnhancement public static void ...
Schließlich bleibt zu erwähnen, dass es auch Annotations gibt, die nur ein Element haben. Diese „Single-Element“-Annotations sollten dann nur das Element value in sich tragen: public @interface ConnectionAddress { String value(); }
Dann kann man den Namen des Elements und das „=“ weglassen und die Adresse dem Annotation Parser einfach so mitgeben: @Connection Address("192.168.0.2") public class Connector {…}
So viel zur Kurzeinführung zum Thema Annotations für TestNG. Interessant ist hier noch, dass am Ende der mittlerweile schon recht betagten Sun-Dokumentation zu lesen ist [1], wie man selbst ein eigenes kleines Testframework bauen kann. Vielleicht kann es Sie ja dazu motivieren, selbst etwas zu entwickeln, das Annotations verwendet. Beispiele wie Hibernate Annotations gibt es zur Genüge. Hinweis Es gibt verschiedene Möglichkeiten, wie Annotations je nach Parameter- und Wertanzahl notiert werden können. Die untenstehende Tabelle gibt daher die wichtigsten Beispiele für die Übergabe von Strings wieder, die in TestNG genutzt werden können.
schnell + kompakt
13
2 – Java Annotations
Ein Wert
@Annotation({„value“}) oder @Annotation(„value“)
Mehrere Werte
@Annotation({„value1“, „value2“})
Ein Parameter
@Annotation(param=“val“)
Mehrere Werte
@Annotation(param={“val1“,“val2“})
Mehrere Parameter und mehrere Werte
@Annotation(param1={„val1“,“val2“,“val3“}, param2={„x1“, „x2“, „x3“})
[1] http://java.sun.com/j2se/1.5.0/docs/guide/language/ annotations.html
14
KAPITEL 3 TestNG – eine Einführung 3.1
Schnellstart
16
3.2
JUnit-Tests verwenden
24
TestNG steht für „Test Next Generation“ analog der Fernsehserie Star Trek Next Generation. Ziel war es, frühzeitig die Annotation-Features von Java 1.5 (Tiger) zu nutzen und ein umfassenderes und besseres Testframework zu schreiben, als es die JUnit-3.*Familie bereitstellt. Entstanden ist ein Framework, das einige Aspekte mehr aufweist als JUnit 4 und vielleicht auch andere Projekte adressiert (siehe Vergleich am Ende des Buches). Cedric Beust startete TestNG 2003, da JUnit einige Mängel in Bezug auf Themen wie Gruppenbildung, Parametrisierbarkeit etc. aufwies und JUnit nicht besonders schnell auf Java Annotations reagierte. Ein Beispiel für den Mangel in JUnit sind die Methoden setUp() und tearDown(). Sie haben einen Klassenscope. Oftmals wünscht sich der Entwickler aber eine feinere (vor und nach jeder einzelnen Methode) oder gröbere Reichweite (vor jeder Testgruppe oder jeder Testsuite). Zudem sollte TestNG auch auf eine Integration mit Ant optimiert sein. Dies entspricht eher dem Zyklus der Continous Integration (Source-Repository, Test, Build, Deploy, Refactoring etc.). Ein Ablauf mittels Testrunner ist zwar schön, passt aber eher nicht in eine professionelle und buildgesteuerte Umgebung. In dieser er-
schnell + kompakt
15
3 – TestNG – eine Einführung
hält man dann mit TestNG viel umfassendere Reports, als dies mit den JUnit Testrunnern möglich ist. Aber es gibt auch Vereinfachungen: So kann man unter TestNG das Schlüsselwort assert für seine Tests nutzen oder beliebige Exceptions werfen, um den Test scheitern zu lassen. Mit logischen Operatoren oder Methoden kann bei der Nutzung von assert jede Methode aus org.testng.AssertJUnit.* nachgebildet werden. Wem dies nicht reicht, der importiert und nutzt einfach die Methoden aus JUnit oder aus anderen Frameworks wie JTiger. Profitipp Das wichtigste Feature unter TestNG ist aber sicherlich, sehr flexibel Testgruppen definieren zu können. Es empfiehlt sich daher, sich bereits während der Entwicklung der Tests unbedingt Kategorien zu überlegen und diese mit Tests zu füllen.
3.1 Schnellstart Ziel dieses Abschnitts ist es, schnell zu Ergebnissen zu kommen. Alle Beispiele wurden unter Eclipse codiert. Erst später schauen wir uns Plug-ins an, die alles noch weiter vereinfachen.
Installation TestNG ist über http://testng.org zu erreichen. Der DownloadBereich enthält einige ältere Versionen, aber an oberster Stelle den letzten Build von TestNG. Heruntergeladen werden kann eine Zip-Datei (hier testng-5.1.zip).
16
Schnellstart
Abb. 3.1: TestNG-Installationsverzeichnis
Kurz eine Erklärung der Verzeichnisse: 쐌 3rdparty: Enthält externe Bibliotheken. (1) bsh-*.jar – enthält die BeanShell-Bibliothek (Java-Interpreter, der dynamisch Java und Skript-Elemente ausführen kann: beanshell.org). (2) qdox*.jar – enthält einen Parser, der aus Java-Dateien entsprechende Klassen/Interfaces oder Methoden zurückliefern kann: qdox.codehaus.org). (3) util-concurrent.jar ist eine Bibliothek ähnlich wie java.util.concurrent, mit Features für den verteilten Zugriff auf Hashmaps, Queues, Semaphoren etc. 쐌 doc: Das doc-Verzeichnis ist leider nur ein Abbild dessen, was auf dem Web unter testng.org/doc/documentation-main.html zu finden ist. 쐌 examples: Dieses Verzeichnis enthält eine Datei, in der die wichtigsten Tags (@Test, @Configuration) mit ihren Parametern aufgeführt sind. 쐌 javadocs: Hier ist die Javadoc-API zu finden. Dies sollte immer verwendet werden, um Parameter und deren Beschreibung nachzuschlagen. Zum Beispiel bietet die Annotation BeforeClass die Parameter alwaysRun, dependsOnGroups, dependsOnMethods, description, enabled, groups und inheritGroups.
schnell + kompakt
17
3 – TestNG – eine Einführung
쐌 src: Enthält einen Teil der TestNG-Sources. Um TestNG selbst zu „builden“, sollten die aktuellen Quellen von http:// code.google.com/p/testng/ gezogen werden. 쐌 test & test-14: Enthält umfangreiche Ant-Buildskripte für Ant unter Java 1.4 und 5. Am wichtigsten sind jedoch die beiden TestNG Jars für Java 1.4 und Java 1.5. Wir verwenden letztere Bibliothek testng-5.1jdk15.jar.
Testfile & Buildfile Wir starten hier einmal entgegen der TDD-Reihenfolge und erstellen die folgende triviale Klasse, die es zu testen gilt: package com.testngbook.ch01; public class Calc { public int add(int a, int b){ return a+b; } public int sub(int a, int b){ return a-b+1; // Fehler } }
In dieser Trivialklasse möchten wir erst die Methode add und danach auch sub testen. Wie bereits erwähnt, arbeitet TestNG hier mit Ant (ant.apache.org) als „Testrunner“, das wir gleich einbinden werden. Als Nächstes benötigen wir die Testdatei:
18
Schnellstart
package com.testngbook.ch01; import org.testng.annotations.*; public class CheckCalc { Calc c = new Calc(); @Test(groups = { "GroupStart" }) public void bTest() { assert c.add(4,5) == 9 : "Kommentar..."; } }
Der erste Import wird benötigt, um das Testtag bekannt zu machen. Wird dieser vergessen, so ist @Test nicht bekannt. In dem Testtag weiter unten kommt eine Gruppe zum Einsatz, die später noch erklärt, aber gleich im Ant-Buildfile aufgerufen wird. Danach verwenden wir das normale Java-Assert. Später zeigen wir, wie man u.a. auch die JUnit-Asserts verwenden kann, falls man auf diese gerne zurückgreifen möchte. Hinweis Zur Erinnerung, TestNG stellt in der Klasse org.testng.AssertJUnit die JUnit-Testmethoden zur Verfügung. Man kann aber auch selbst Exceptions werfen, das Java-Schlüsselwort assert nehmen oder auf andere Methoden von Frameworks wie JTiger zurückgreifen. Interessant ist hierbei natürlich auch, dass die Testmethode keinen vorgeschriebenen Namen mehr haben muss. Hier heißt sie einfach bTest.
schnell + kompakt
19
3 – TestNG – eine Einführung
Wir verwenden nun ein minimales Buildfile: <project default="test"> <path id="cp"> <pathelement location="C:/testng-5.1/ testng-5.1-jdk15.jar" /> <pathelement location="." />
Was enthält dieses Buildfile? Zunächst ein Projekt mit dem Defaulttarget „test“. In den folgenden Path-Definitionen werden zwei Pfade bekannt gemacht: erstens der Ort des TestNG-Jars und zweitens der Pfad, unter dem gearbeitet werden soll, d.h., wo die Quellen liegen. Beide müssen unbedingt angepasst werden und vollständig sein. Profitipp Wenn man in seinen Tests externe Bibliotheken nutzen möchte, beispielsweise für Mock-Bibliotheken wie easymock.jar oder iText (itext.jar), dann muss man diese hier unbedingt im Classpath (path) angeben. Sonst findet TestNG diese nicht und man erhält Exceptions, die wenig aussagekräftig sind.
20
Schnellstart
Danach wird der TestNG Ant Task bekannt gemacht und als Nächstes gleich im Target „test“ verwendet. Auch hier muss der dir-Pfad (wo liegen die Klassen?) stimmen. Includes gibt die Klassen an, die die Tests enthalten. Angenehm ist, dass man unter Ant intensiv mit Wildcards arbeiten kann. Es kann also durchaus einfach includes="com/testngbook/ch01/*.class"
angegeben werden und die Testmethoden werden ebenfalls gefunden.
Testergebnis Wenn das Ant-Buildfile mit dem Target „test“ nun über die Konsole oder über die IDE aktiviert wird, dann wird lediglich mitgeteilt, dass ein Testlauf durchgeführt wurde, kein Test scheiterte und kein Testlauf übersprungen wurde. Wer hier JUnit aus einer IDE gewohnt ist, der vermisst einen Testrunner, der einem mindestens gleich die grüne Farbe bringt. Hier hat aber TestNG bereits viel mehr zu bieten, ohne dass man es auf den ersten Blick sieht: Im Home-Verzeichnis von Ant ist still und leise ein Verzeichnis test-output erstellt worden. Jetzt sollte man einfach mal schnell das dort enthaltene index.html in einen Webbrowser werfen und staunen. Als Erstes fällt sicherlich auf, dass uns das altbekannte Grün aus JUnit wieder anstrahlt. Wir können uns dann durch alle Testgruppen, Methoden, Klassen etc. durchklicken und sogar die Stack-Traces bei Bedarf einblenden. Um diesen Schnelleinstieg abzuschließen, integrieren wir noch kurz ein Beispiel, in dem der Test scheitert. Wir hatten ja bereits eine falsche sub-Routine in Calc eingefügt, die wir jetzt nutzen
schnell + kompakt
21
3 – TestNG – eine Einführung
wollen. Dazu fügen wir hinter den vorigen bTest einfach den zweiten Test ein: @Test(groups = { "GroupStart" }) public void cTest() { int x=7; int y=4; assert c.sub(x,y) == x-y : x+"-"+y +"=" + c.sub(x,y); }
Wir verweisen also wieder auf die gleiche Gruppe GroupStart. Im Test nehmen wir einmal nicht feste Zahlen, sondern spaßeshalber Variablen. Dann fällt es leicht, dem assert noch eine Beschreibung mitzugeben, die ausgibt, was in diesem Test eigentlich passiert. Als Ergebnis teilt Ant in der Konsole lediglich mit: Total tests run: 2, Failures: 1, Skips: 0
Und der Report im Browser zeigt uns in Results unter anderem auch exakt den AssertionError mit der Assertion-Meldung, die uns genau sagt, wo der Wurm drin sein muss: java.lang.AssertionError: 7-4=4
Wir erhalten also erst mal keine Farbe aus dem Ant-Ergebnis, was schade ist. Aber dennoch können wir im Ant-Output sofort sehen, ob etwas schief gegangen ist. Details gibt es dann schnell im test-output / index.html-Report, den man im Browser immer offen halten kann. Mit einem Refresh des Browsers lässt sich dieser sofort aktualisieren, wenn Ant durchgelaufen ist. Auf die Nutzung von TestNG mittels Ant wird im Kapitel 7 „Automatisierung und Buildtools“ ausführlicher eingegangen.
22
Schnellstart
Assertions nutzen An dieser Stelle sei noch einmal auf die verschiedenen Möglichkeiten hingewiesen, wie unter TestNG dann wirklich getestet werden kann: 1. Mit Javas assert @Test public void myTest(){ assert myInst.machWas(3,4),9 : "Kommentar" }
2. Mit den JUnit-äquivalenten Methoden assert* @Test public void myTest(){ assertEquals (myInst.getIPstr(), "137.234.23.4"):"Kommentar" }
Hierbei stehen die folgenden Methoden zur Verfügung: assertEquals, assertNotNull, assertNull, assertSame, assertNotSame, fail, assertFalse und assertTrue. Sie sind bereits in der Klasse org.testng.Assert integriert.
3. Eine Exception werfen @Test public void myTest(){ if(... condidion...) throw new RuntimeException( "Test scheitert weil ..."); }
schnell + kompakt
23
3 – TestNG – eine Einführung
4. Andere Assertions aus anderen Frameworks importieren, zum Beispiel die reichhaltigen aus JTiger
3.2 JUnit-Tests verwenden JUnit-Tests können relativ einfach unter TestNG weiterverwendet werden. Man gibt in der Datei testng.xml die Testklassen an, die Methoden enthalten, die mit dem Wort "test" beginnen. TestNG führt diese dann aus.
Die Methoden setUp() und tearDown() werden ebenfalls vor und nach der Testklasse ausgeführt. Genauso können TestsuitenKlassen ausgeführt werden. Die einzige Falle, in die man hier auch wieder tappen kann, ist, zu vergessen, TestNG die JUnit-Bibliothek zu übergeben. Daher muss in der Ant-Datei der Classpath richtig gesetzt sein. Also z.B. so: <pathelement location="C:/junit-4.1.jar" />
24
KAPITEL 4 XML-Konfigurationen 4.1
Die DTD verstehen
29
4.2
Suite
29
4.3
Test
31
4.4
Classes
32
4.5
Packages
32
4.6
Methods
33
4.7
Groups und Runs
34
4.8
Parameter
36
TestNG kann auch über die Konsole gestartet werden. Dazu wird eine XML-Datei übergeben, die dann von TestNG ausgewertet und ausgeführt wird. Bevor wir uns die Struktur der XML-Datei genau anschauen, starten wir wieder mit einem einfachen Beispiel. Hinweis Es gibt drei Arten, TestNG zu starten: 1) TestNG kann wie zuvor gesehen unter Ant gestartet werden, wobei die Klassen als FileSet direkt oder als Patterns angegeben werden. 2) Die im Folgenden vorgestellte XML-Datei kann von der Konsole aus gestartet werden. Alle Tests und Abläufe (runs) sind dann in dieser XML-Datei definiert. 3) Am häufigsten allerdings wird die XML-Datei aus Ant gestartet. Dazu wird ein xmlfileset übergeben. Details zu allen Ant-Varianten sind im Kapitel über Buildtools nachzulesen. Zusätzlich ist es möglich, Dateien in der Konsole anzugeben. Der Start via IDE wird im entsprechenden Kapitel beschrieben.
schnell + kompakt
25
4 – XML-Konfigurationen
Spaßeshalber kopieren und refaktorieren wir unsere vorige Testdatei CheckCalc zu CheckCalc2. Die Gruppenangaben hinter @Test sind in diesem Beispiel nicht mehr nötig. Sie können gelöscht werden, stören aber auch nicht. Um jetzt per Konsole zu starten, müssen die Testklassen in einem XML-File (kein Ant Buildfile mehr) spezifiziert werden: <suite name="Suite1" verbose="1" >
Üblicherweise heißt diese Datei testng.xml. Hinweis In dieser Datei dürfen Sie A) nicht die erste Zeile vergessen, bei der man die DTD angibt, und B) sich nicht verschreiben (z.B. zuerst class statt classes). In allen Fällen erhält man doch recht unverständliche SAX-Exceptions. Wenn wir uns nun in das Home-Verzeichnis unseres Projekts begeben, sollten wir zum Start in der Kommandozeile erst einmal den Klassenpfad überprüfen bzw. setzen: set CLASSPATH=.;C:\TestNG-5\testng-5.0.2-jdk15.jar
26
XML-Konfigurationen
Dies gilt für Windows. Ein Aufruf unter Linux mit z.B. export PATH=${PATH};…TestNGpath… ist fast äquivalent.
Mit diesem Jar kann die Klasse TestNG gefunden werden, die uns in der Konsole folgenden Test ermöglicht: java org.testng.TestNG testng.xml
Und das Ergebnis ist: ============================================== Suite1 Total tests run: 4, Failures: 0, Skips: 0 ==============================================
Nun, zumindest funktioniert der Aufruf, aber wieso klappen alle Tests, wo doch ein eingebauter Fehler im Calc.java war? Ganz einfach, wir haben hier vergessen, die Assertions einzuschalten! Profitipp TestNG ist es letztlich egal, welches Hilfsmittel man in den Testmethoden verwendet. Man sollte sich daher später grundsätzlich schon überlegen, ob man statt Java Assertions (wie hier verwendet) nicht JUnit Asserts nehmen möchte oder fremde/selbst entwickelte Methoden zum eigentlichen Testen verwendet. Also noch einmal: java -ea org.testng.TestNG testng.xml
Und es scheitern wie erwartet die zwei Tests. Auch hier sollten wir uns noch mal das test-output/index.html ansehen.
schnell + kompakt
27
4 – XML-Konfigurationen
Für diesen Ablauf hätten wir auch die Testklasse in einen zweiten Test tun können: ... suite ...
... suite ...
Am Endergebnis hätte dies nichts geändert, nur eine leicht geänderte Darstellung im HTML-Report. Die wichtigsten weiteren Parameter beim direkten Programmstart sind: 쐌 -d /usr/tom/myReport/
Das Zielverzeichnis für die Reports: 쐌 -testclass "de.book.testc1,de.book.testc2"
Es können weitere Testklassen angegeben werden, die ausgeführt werden sollen. Dies geschieht üblicherweise nur dann, wenn nicht direkt Packages angegeben werden können. 쐌 -groups "smoke,database,gui" 쐌 -excludegroups "smoke,database,gui"
Diese beiden Optionen erlauben es, die Gruppen anzugeben, die ausgeführt werden sollen.
28
Die DTD verstehen
4.1 Die DTD verstehen Was alles in dieser XML-Datei möglich ist, ist ohne eine Grafik kaum zu verstehen. Daher gibt die Abbildung 4.1 eine gute Übersicht über die Hierarchie der Elemente. Schauen wir uns die Elemente der Reihe nach an:
Abb. 4.1: Hierarchie der XML-Elemente
4.2 Suite Jede XML-Datei wird mit <suite> gestartet und mit beendet. Interessant sind hier zunächst die vier Parameter: 쐌 name: Der Name der Testsuite. Wird hier ein Name angegeben, ist dieser dann im HTML-Report zu sehen. 쐌 verbose: Hier wird eine Zahl von 0 bis 10 angegeben. Level 10 schreibt detailliert, welche Klassen und Methoden aufgerufen und welche Verzeichnisse für den Report erstellt werden. Am
schnell + kompakt
29
4 – XML-Konfigurationen
beliebtesten ist der Defaultmode 1, da er lediglich den Namen der Suite, die Runs, die Fehler und die übersprungenen Tests mit Skip ausgibt. 쐌 parallel: Hiermit wird ein boolean true oder false übergeben. Bei true kann TestNG verschiedene Threads verwenden, um alle Tests auszuführen, was die Ausführung beschleunigen kann. 쐌 thread-count: Hier wird ein Integer angegeben, der die Anzahl der Threads zur Bearbeitung angibt. Wird bei parallel true angegeben, aber kein thread-count, so wird hier per Default mit fünf Threads gearbeitet. 쐌 annotations: Die letzte Option erlaubt es, zu spezifizieren, ob Java-5-Annotations (JDK5) oder javadoc-Annotations gesucht werden. Hier ein Beispiel, welches alle fünf Parameter verwendet: <suite name=“Integration“ verbose=“10“ parallel=“true“ thread-count=“16“ annotations=”JDK5”> ...
Das Wichtigste an dem suite-Tag ist aber, dass es eine Menge von Tests/Test-Tags enthalten sollte. Dies entspricht also einer Aufzählung bzw. Definition aller Tests, die man durchführen möchte: <suite name=“AllTests“>
... classes, packages oder groups ... classes, packages oder groups ...
30
Test
4.3 Test Die Parameter name, verbose, parallel und annotations sind bei dem Testelement der XML-Datei identisch wie bei dem Suite-Element. Man gibt dem Test also einen Namen, kann mit verbose den Logging-Level von TestNG verändern (oder den Level der Suite überschreiben), mit parallel mehrere Threads aktivieren (merkwürdigerweise aber auf der Testebene kein thread-count angeben) oder wieder die Art der Annotations spezifizieren. Als weiteres Tag kann junit auf true oder false gesetzt werden. Damit können auch normale JUnit-Tests ausgeführt werden. Ist in einem XML-File also z.B.
… gesetzt, so werden alle Methoden gesucht, die mit test beginnen, und ausgeführt. Weiterhin werden Methoden mit den Namen setUp() und tearDown() gesucht und ausgeführt. Dieses angenehme Feature aus TestNG funktioniert ebenfalls, wenn die Klasse eine Testsuite ist und eine Menge von JUnit-Tests zurückliefert. Hinweis Es empfiehlt sich immer, einmal einen Blick in Beispiele zu werfen. So ist bei TestNG im Verzeichnis /test eine Beispieldatei zu sehen. Anhand dieser Datei bekommt man schon einmal ein gutes Gefühl für die Hierarchien und Anwendungsfälle, die mit dem XML-File abgedeckt werden können. Das wichtigste Tag Test enthält im wesentlichen Packages, Classes und Groups. Seit Version 4.5 sind noch Method Selectors hinzugekommen.
schnell + kompakt
31
4 – XML-Konfigurationen
4.4 Classes Innerhalb eines XML-Test-Tag muss das Tag kommen. Erst darin sind wiederum die einzelnen Testklassen mit angegeben. <suite name="MiddleTier"> ... <packages> <package name="de.testngbook.A"/> <package name="de.testngbook.B"/> ... <package name="de.testngbook.Z”/> packages >
4.6 Methods Die Schlüsselwörter include und exclude werden im Wesentlichen auf die Methoden von Klassen angewendet. Dazu ein Beispiel: suite... test... <methods> <exclude name="doYcorrect"/> .../test... ...suite...
schnell + kompakt
33
4 – XML-Konfigurationen
Defaultmäßig werden hier alle Tests einer Klasse genommen. Mit exclude können einzelne Methoden, die bereits mit „als Test“ markiert worden sind, herausgenommen werden.
4.7 Groups und Runs Eine Group ist immer Teil eines Tests: ...
In diesem groups-Tag wird nun entweder genauer definiert, welche Testgruppen ausgeführt werden sollen, oder es wird eine Gruppe aus Testgruppen definiert. Schauen wir uns den ersten Fall an: Hier greifen wir dem nächsten Kapitel der Annotations etwas vor. Jeder Tests kann ganz einfach mit einem Gruppennamen versehen werden, z. B. @Test(groups={"conversions"}. Wenn etwas ausgeführt werden soll, so sind dies runs, also Methoden, die ablaufen. Also wird in der Group ein run definiert: <suite name="System2">
34
Groups und Runs
... <packages> <package name="de.testngbook"/>
Das run enthält beliebig viele includes, d.h. Methoden, die mit diesem Tag markiert sind. Erst danach müssen classes oder packages definiert werden, auf welche sich diese run-Gruppen beziehen. Als letzten Fall definieren wir nun eine Gruppe aus Gruppen. Diese werden Meta-Groups genannt: <define name="dbTest"> <define name="total">
Die Testgruppe dbTest wird also in einer anderen Gruppe total wieder verwendet.
schnell + kompakt
35
4 – XML-Konfigurationen
4.8 Parameter Parameter können auf dem suite- oder test-Level definiert werden. Damit können statisch Daten an den Test übergeben werden. Testdaten lassen sich also in der XML-Datei ändern, ohne dass der Test verändert werden muss. Parameter, die auf dem Testlevel definiert worden sind, überschreiben die des suiteLevels. Hier ein Beispiel: <parameter name="ip" value="168.24.153.5"/> <parameter name="tcp" value="1099"/>
Ein komplettes Beispiel mit Anwendung ist im nächsten Annotations-Kapitel bei der Annotation @Parameter zu finden.
36
KAPITEL 5 TestNG Annotations 5.1
@Test
37
5.2
Weitere Parameter
43
5.3
Aufrufe vor und nach Tests
45
5.4
Parameterübergabe in den Test
51
5.5
DataProvider
52
5.6
Test Factories
56
In den vorigen Kapiteln haben wir bereits eine TestNG Annotation kennen gelernt: @Test. Es ist nun an der Zeit, die wichtigsten weiteren Annotations und Parameter im Einsatz kennen zu lernen.
5.1 @Test Die wichtigste Annotation markiert eine Klasse als Testklasse für eine andere Klasse oder nur eine Methode als Testmethode. Die Annotation muss mit import org.testng.annotations.Test
bekannt gemacht werden. Dieses Tag kann man ohne jeden Parameter verwenden.
schnell + kompakt
37
5 – TestNG Annotations
Parameter groups @Test(groups="regression")
Der wichtigste Parameter ist sicherlich groups. Mit der Angabe dieses Parameters wird die Methode in dieser Kategorie/unter diesem Namen geführt. So können in der XML-Datei einfach eine Menge von Tests verwendet werden.
Parameter dependsOnGroups und dependsOnMethods Es folgen nun zwei Parameter für Abhängigkeiten. Das heißt, die Testklasse hat Dependencies, wie man es im Java-Umfeld aus Ant-Build-Dateien kennt. Es sollten also zuerst die abhängigen Testklassen ausgeführt werden und dann die vorliegende Testklasse: @Test(groups="smoke") public void testFirst(){ System.out.println("FirstMe!"); assert ...; } @Test(dependsOnMethods="smoke") public void checkGetConn(){ assert ...; }
Hier hängt die Testmethode von einer Testklasse der Gruppe smoke ab und wird vorher ausgeführt.
38
@Test
Hinweis Es geht hierbei nur um public-Testklassen oder public-Testmethoden, die vorher ausgeführt werden sollen. Ganz normale Methoden wie setUp können mit anderen Annotations wie @beforeMethod aufgerufen werden und werden später erklärt. Der Aufruf einer abhängigen Methode sieht beispielsweise so aus: @Test public void testFirst(){ System.out.println("FirstMe!"); assert...; } @Test(dependsOnMethods="testFirst") public void checkGetConn(){ assert ...; }
Seit Version 4.5 kann dies sogar klassenübergreifend geschehen (dazu muss der Test auch im XML-File bekannt sein!). Es kann also eine konkrete Methode so übergeben werden: @Test(dependsOnMethods="de.testngbook.CheckProcessA.doXcorrect")
Die Idee dieser abhängigen Methoden ist die folgende: Wenn testFirst zuerst ausgeführt wird und klappt, dann gibt es 2 runs (zwei Testmethoden, die ausgeführt werden sollen), 0 failures (kein gescheiterter Test) und 0 skips (also kein übersprungener Test). Scheitert nun aber die abhängige Methode wie im vorliegenden Fall, dann sieht das Ergebnis anders aus:
schnell + kompakt
39
5 – TestNG Annotations
@Test public void testFirst() { System.out.println("First me!"); ... throw new IllegalArgumentException(); } @Test(dependsOnMethods="testFirst") public void checkGetConn(){ System.out.println("Then me!"); assert ...; }
Mit diesem Fall ergeben sich ebenfalls 2 runs, aber jetzt 1 fail und 1 skip. Das heißt, der abhängige Test (testFirst) scheitert jetzt, so dass der eigentliche Test (checkGetConn) jetzt nicht mehr ausgeführt wird. Profitipp Sowohl Abhängigkeiten von Gruppen als auch Abhängigkeiten von Methoden können angegeben und gemischt werden.
Parameter expectedExceptions Der Parameter expectedException ist ideal für whiteBox-Tests. Bei diesen weiß man, was in der zu testenden Klasse vor sich geht, und kann entsprechend reagieren. Das typische Beispiel ist, dass eine Methode eine Exception wirft, wenn ein falscher Parameter übergeben wird. Dies sollte ja beispielsweise meistens geschehen, wenn public-Methoden null als Parameter bekommen. Unter JUnit hat man dazu früher Folgendes implementiert:
40
@Test
try{ assert … ausführen des Tests } catch(Exception erwarteteException){ …tue nichts… } fail(); // Test wird zum Scheitern gezwungen
Unter TestNG kann nun einfach die erwartete Exception angegeben werden: @Test(expectedExceptions={IllegalArgumentException.class}) public void checkGetConn(){ assert tmp.getConn(null,...)==...; }
Funktioniert nun die Methode getConn wie erwartet und wirft eine Exception, wenn hier null übergeben wird, dann ist der Test positiv ausgegangen. Wir erhalten also: Total tests run: 1, Failures: 0, Skips: 0
Und dies ist der Code, der die Exception werfen soll, wenn null übergeben wird: public int getConn(String arg){ if(arg==null) throw new IllegalArgumentException(); return ...; }
Umgekehrt wirft TestNG eine Exception, wenn die Zeilen 2 bis 3 oben fehlen würden. Das Akzeptieren eines null-Arguments (was in der Regel fatal ist) quittiert TestNG also mit:
schnell + kompakt
41
5 – TestNG Annotations
Expected an exception in test method de.test..
Parameter dataProvider Mit einem DataProvider können der Testklasse komplexere Daten/Objekte übergeben werden: @Test(dataProvider = "produce")
Die Methode produce sollte also von TestNG gefunden werden können und kann dann der eigentlichen Testklasse ObjectArrays oder Iteratoren zur Verfügung stellen, die der Testklasse übergeben werden. Ein Beispiel wird etwas später angegeben, wenn die Annotation @DataProvider besprochen wird.
Parameter alwaysRun Ein paar Seiten vorher hatten wir die Parameter der Dependencies beschrieben. Bei dependsOnGroups und dependsOnMethods wurde die Testmethode nicht ausgeführt, wenn die angegebenen abhängigen Methoden scheitern. Dies kann mit alwaysRun ausgeschaltet werden: @Test public void testFirst() { System.out.println("First me!"); throw new IllegalArgumentException(); } @Test(dependsOnMethods="testFirst", alwaysRun=true) public void checkGetConn(){ System.out.println("Then me!"); }
42
Weitere Parameter
Hier scheitert testFirst und checkGetConn wird korrekt ausgeführt. Dies wird daher meistens verwendet, um zu sehen, ob die Tests auch weiter durchlaufen würden.
5.2 Weitere Parameter Es gibt noch eine Reihe weiterer Parameter, die seltener verwendet werden und hier nur kurz angesprochen werden sollen: 쐌 enabled: Mit diesem Parameter können Testklassen quasi einund ausgeschaltet werden. Die Zeile Beispiel:
@Test(enabled=false)
hat daher bei einer Methode zur Folge, dass diese nicht als Testmethode in jeden Run aufgenommen wird. Liegt dieses Tag bei einer Klassenannotation vor, dann werden alle Methoden ebenfalls ein- und ausgeschaltet. Hinweis Ist die Methode jedoch selbst wieder mit @Test markiert worden, so hat das Flag an der Klassenannotation keine Wirkung! 쐌 description: Hiermit kann die Testklasse näher beschrieben werden: Beispiel:
@Test(description="Smoke Test für Tom")
Diese Beschreibung wird dann fett im HTML-Report unter den Bericht gesetzt, was bei der Testanalyse immer sehr wertvoll ist.
schnell + kompakt
43
5 – TestNG Annotations
쐌 invocationCount: Hiermit kann angegeben werden, wie oft die betreffende Methode aufgerufen wird. Beispiel:
@Test(invocationCount=2) public void checkGetConn(){...}
Mit diesem Parameter wird die Testmethode zweimal aufgerufen. Dies ist immer dann ideal, wenn die Methode nicht garantiert deterministisch ist („Funktioniert die Netzwerkverbindung auch zuverlässig?“) oder die Eingabedaten variieren. Die Daten für die zu testende Methode können durchaus dynamisch sein und das Ergebnis des Tests beeinflussen. 쐌 successPercentage: Dieser Parameter ist ein Hinweis für TestNG, dass die mehrfache Ausführung dieses Tests (z.B. mit dem vorigen invocationCount) durchaus einmal scheitern kann. Beispiel:
@Test(invocationCount=10, successPercentage=100)
Scheitern von diesen Tests einige und einige klappen, so wird diese Prozentzahl von 0 bis 100 von TestNG gelesen und der Report entsprechend gestaltet. Wenn beispielsweise nur 90 Prozent erwartet werden und tatsächlich 9 von 10 Tests erfolgreich sind, dann wird dies in verschiedenen Grüntönen dargestellt. Es geht hier also um statistisches Testen, wenn ein nicht deterministischer Test auch ruhig mal scheitern darf. 쐌 sequential: Erzwingt die sequenzielle Ausführung der Testmethoden einer Klasse: Beispiel:
@Test(sequential=true)
Auch wenn der Test selbst mit Parallelausführung (parallel=true) gestartet wurde, werden alle Methoden der Klasse
44
Aufrufe vor und nach Tests
sequenziell ausgeführt. Auf Methoden ist dieses Kommando nicht anwendbar. 쐌 timeout: gibt die maximale Zeit an, die der Test benötigen darf. Beispiel:
@Test(timeout=10000)
Wird diese Zeit überschritten, so wird der Test als fehlerhaft markiert. 쐌 threadPoolSize: gibt an, wie viele Threads für die Ausführung dieser Methode bereitgestellt werden sollen. Beispiel:
@Test(threadPoolSize=10)
Dieser Parameter ist natürlich nur sinnvoll, wenn der Parameter invocationCount auf einen höheren Wert gesetzt wurde. In diesem Fall wird genau eine Methode mehrfach auf mehreren Threads ausgeführt. Dies kann z.B. für Stresstests eingesetzt werden.
5.3 Aufrufe vor und nach Tests Viele Testentwickler kennen die aus JUnit bekannten Methoden setUp() und tearDown(). Das heißt, es sind Ausführungen vor und nach Tests nötig. In TestNG gibt es fünf verschiedene Granularitätsstufen, in denen solche Pre- und Post-Ausführungen möglich sind:
Abb. 5.1: Die fünf Granularitätsstufen in TestNG
schnell + kompakt
45
5 – TestNG Annotations
Insgesamt sind dies zehn Annotations für vorher und nachher. Angeführt sei als Erstes ein einfaches Beispiel, in dem wir die analogen Testmethoden setUp() und tearDown() unter TestNG verwenden.
@BeforeClass und @AfterClass Wir betrachten das folgende Beispiel: @BeforeClass public void likeSetUp() { ...Zuerst Verbindung aufbauen... } @Test() public void checkSendData(){ ...Dann Daten senden... } @AfterClass public void likeTearDown(){ ...Verbindung schliessen!... }
Dieses Beispiel kann recht einfach mit Systemausgaben getestet werden. Im Ergebnis sieht man, dass die Methoden wie erwartet in der richtigen Reihenfolge aufgerufen werden. Wir haben daher hier beispielsweise eine solche Aufrufreihenfolge: 쐌 쐌 쐌 쐌
Vorher = @BeforeClass Test1 = @Test Test2 = @Test Nachher = @AfterClass
46
Aufrufe vor und nach Tests
Es werden also alle Testmethoden einmal umschlossen. Profitipp Es können mit diesen Annotations beliebig viele Methoden vor allen Tests aufgerufen werden – und nach diesen Tests auch beliebig viele Methoden.
@BeforeMethod und @AfterMethod Mit den Annotations @BeforeMethod und @AfterMethod erhalten wir eine andere Reihenfolge der Aufrufe: 쐌 쐌 쐌 쐌 쐌 쐌
Vorher = @ BeforeMethod Test1 = @Test Nachher = @ AfterMethod Vorher = @ BeforeMethod Test2 = @Test Nachher = @ AfterMethod
Es werden hierbei beim Aufruf jeder Methode die Pre- und PostMethoden aufgerufen. Diese Art von Aufruf eignet sich beispielsweise dafür, wenn Testmethoden ständig neue (Zufalls-) Daten benötigen.
Suite- und Test-Level TestNG hört nicht auf dem Level der Klassen auf. Mit den Annotations @BeforeSuite, @BeforeTest, @AfterSuite und @AfterTest stehen vier weitere Annotations auf höherer Ebene zur Verfügung. Hierbei geht es meistens um globalere Initialisierungen und Finalisierungen, die Test- und Suite-übergreifend sind. Schauen wir uns beide Tags an einem Beispiel an. Angenommen, es liegt folgendes TestNG-XML-File vor:
schnell + kompakt
47
5 – TestNG Annotations
<suite name="SMOKE"> <packages> <package name="de.testngbook.middletier"/> <package name="de.testngbook"/>
Ist nun in einer Klasse im Package de.testngbook eine @BeforeTest-Annotation vor einer Methode, dann wird erst diese ausgeführt und dann alle anderen Tests (z.B. die aus middletier) dieser Testdefinition in XML. @BeforeSuite funktioniert analog dazu: <suite name="SMOKE"> <packages> <package name="de.testngbook.middletier "/> <packages> <package name="de.testngbook "/>
Das heißt, eine mit @BeforeSuite ausgezeichnete Methode aus de.testngbook/dem Test B2 wird vor dem Test B1, also vor den Testmethoden aus de.testngbook.middletier, ausgeführt.
48
Aufrufe vor und nach Tests
@BeforeGroup und @AfterGroup Die mit @BeforeGroup und @AfterGroup annotierte Methode wird vor dem Ausführen einer anderen Methode mit der angegebenen Methode ausgeführt. Dieses relativ neue Feature ist anscheinend nur innerhalb einer Klasse gültig. Als Beispiel führen wir eine Testklasse aus, die irgendwo in einem Package liegt. Das XML-File sieht also so aus: …suite…test… <packages> <package name="de.testngbook.backend"/> …/test…/suite…
Unsere Testklasse sieht jetzt so aus: @Test(groups = {"GruppeA"}) public void testeEtwas() { System.out.println("teste etwas..."); } @Test(groups = {"GruppeB"}) public void testeAnderes() { System.out.println("andere Gruppe..."); } @AfterGroups("GruppeA") private void aufraeumen() { System.out.println("aufräumen..."); }
schnell + kompakt
49
5 – TestNG Annotations
@BeforeGroups("GruppeA") private void initialisieren() { System.out.println("initializiere"); }
Und wir erwarten dieses Ergebnis: [testng] [testng] [testng] [testng]
initializiere teste etwas... aufräumen... andere Gruppe...
Vor und nach dem Aufruf der Methode testeEtwas, die zu GruppeA gehört, werden also die jeweiligen @BeforeGroups (initialisieren) und @AfterGroups (aufräumen) aufgerufen. Die weitere Methode der GruppeB wird irgendwann aufgerufen.
Parameter für *Before* und *After* An Parametern kennen diese zehn vorgestellten Annotations, fünf bereits bekannte und einen neuen Parameter. Die bereits vor einigen Seiten für die Annotation @Test vorgestellten Parameter sind: 쐌 alwaysRun: Dieser Methodenaufruf findet immer statt – vorher sowieso und nach Testmethoden (Annotation @After*), auf jeden Fall, wenn true gesetzt ist. 쐌 dependsOnGroups, dependsOnMethods: Falls die annotierte Methode seinerseits wieder von anderen Gruppen oder Methoden abhängt, so werden diese vorher gestartet. Dies ermöglicht zweifache Indirektionen (vor dem Test eine Methode, die wiederum von Gruppen abhängt), die in den seltensten Fällen gebraucht wird.
50
Parameterübergabe in den Test
쐌 enabled: Mit diesem Flag können die @Before- und die @AfterAbhängigkeit ein- und ausgeschaltet werden. 쐌 groups: Gibt an, zu welcher Gruppe die Methode gehört. Neu ist hingegen der Parameter inheritGroups, mit dem die Methode die Gruppenzugehörigkeit ihrer Klasse erbt. Ein Beispiel: @Test(groups="beta") public class Mixer { @BeforeMethod(inheritGroups=true) private void setMeUp() { ... }
Hier erbt die Methode setMeUp() die Gruppe beta und kann daher auch von anderen runs gestartet werden (siehe Gruppen in der Steuerung des XML-File).
5.4 Parameterübergabe in den Test Normalerweise haben Testmethoden keinen Parameter. In einem guten Testframework kann man jedoch Werte außerhalb von Java – also hier aus der XML-Datei – in die Testmethode übergeben. Dazu ein Beispiel: public class RMIconnectTester { @Parameters( { "ip", "tcp" }) @Test public void testConn(String ip, String tcp) { System.out.println("IP=" + ip + " TCP=" + tcp); ... mache etwas mit den Adressen ... } }
schnell + kompakt
51
5 – TestNG Annotations
Die Testmethode testConn soll aus der XML-Datei zwei Parameter empfangen. Diese werden einfach mit der Annotation @Parameter definiert. Die Methode definiert dann zwei Argumente mit dem gleichen Namen. Nun muss lediglich die XML-Datei erweitert werden: <suite name="SMOKE"> <parameter name="ip" value="168.24.153.5"/> <parameter name="tcp" value="1099"/> <packages> <package name="de.testngbook.middletier"/>
Und der Test erhält die Werte aus der XML-Datei und kann diese hier zu Testzwecken ausgeben.
5.5 DataProvider In vielen Fällen reicht eine Datenübergabe aus der XML-Datei nicht aus. Oftmals muss beispielsweise ein Mock-Objekt in den Test übergeben werden, welches einen viel komplexeren Aufbau hat. Oder die Daten kommen einfach aus ganz anderen Quellen wie Datenbanken oder TestNG-fremden XML-Dateien.
Feste Argumentenanzahl Hier hilft TestNG recht einfach, um beliebig viele komplexe Objekte zu übergeben. Zunächst ein einfaches Beispiel, das dem des Manual ähnelt und obige TCP/IP-Daten verwendet:
52
DataProvider
public class RMIconnectTester { @DataProvider(name = "Aprovider") public Object[][] provideMock() { return new Object[][] { {"165.22.184.4", new Integer(1099)}, {"149.123.50.66", new Integer(1098)},}; } @Test(dataProvider = "Aprovider") public void testConn(String ip, Integer tcp) { System.out.println("IP=" + ip + " TCP=" + tcp); //... mache etwas mit diesen Adressen } }
Ergibt die folgende Ausgabe: [testng] IP=165.22.184.4 TCP=1099 [testng] IP=149.123.50.66 TCP=1099
Das bedeutet, dass die Testmethode so oft aufgerufen wird, wie die erste Array-Dimension es vorsieht. Die zweite Dimension ist dann die Menge der Argumente, die die Methode testConn vorsehen muss. Dort kann auch einfach nur ein Objekt übergeben werden (siehe „Mock-Objekte übergeben“ weiter unten).
Iteratoren übergeben Ist die Anzahl der Parameter aber nicht konstant, sondern variabel, dann kommt man mit obigem Mechanismus nicht mehr weiter. Dann erlaubt es TestNG, Iteratoren zu übergeben.
schnell + kompakt
53
5 – TestNG Annotations
@DataProvider(name = "Iprovider") public Iterator dataProvider() { Vector v = new Vector(); v.addElement(new Object[] {"173.34.25.193", new Integer(1099)}); v.addElement(new Object[] {"89.184.52.2", new Integer(1089)}); return v.iterator(); } @Test(dataProvider = "Iprovider") public void testConn(String ip, Integer tcp) { System.out.println( ip + "" + tcp); // ... mache etwas mit den Daten ... }
Wichtig ist auch hierbei wieder, nicht zu vergessen, dass sich TestNG den Iterator holt. TestNG ruft dann jedes Mal den Iterator mit .next() auf und erwartet ein Object[] als Rückgabewert. Die Elemente aus dem Object[] müssen dann genau zur Signatur der Testmethode passen.
Parametrisierte DataProvider Im ersten Beispiel des DataProvider mit fester Argumentenanzahl hatten wir nur den Fall, dass eine Testmethode einen DataProvider aufruft. Und der DataProvider kann die Testmethode so oft aufrufen, wie der Objectarray im Returnvalue groß ist. Aber natürlich können auch verschiedene Testmethoden diesen DataProvider nutzen. Alle Testmethoden werden dann genauso mit den Rückgabewerten des einen DataProvider aufgerufen. Interessant ist die Variante, in der der DataProvider selbst parametrisiert werden kann, so dass er quasi weiß, welche Testme-
54
DataProvider
thode er aufrufen wird und dementsprechend andere Daten in die Testmethode übergeben kann. In dem folgenden Beispiel soll der RMI-Dienst getestet werden. Es gibt zwei Testmethoden: 1) testActivation und 2) testRegistry. Erstere soll den Port 1098 und zweite den Port 1099 übergeben bekommen: @DataProvider(name = "rmi") public Object[][] createData(Method m) { if (m.getName().equals("testActivation")) return new Object[][] { new Object[] { new Integer(1098) } }; if (m.getName().equals("testRegistry")) return new Object[][] { new Object[] { new Integer(1099) } }; return // Fehlerfall }; } @Test(dataProvider = "rmi") public void testActivation(Integer port) { System.out.println("AConnect on TCP " + port); } @Test(dataProvider = "rmi") public void testRegistry(Integer port) { System.out.println("RConnect on TCP " + port); }
Hier bekommt der DataProvider eine Variable des Typs java. lang.reflect.Method übergeben, mit der (getName()) der Name der aufrufenden Methode abgefragt werden kann. Dann können der Testmethode unterschiedliche Daten übergeben werden. Diese Variante ist daher ideal, wenn ein DataProvider mehreren Testmethoden verschiedene, aber vielleicht ähnliche Daten übergeben soll. Diese können vielleicht generiert sein oder aus anderen Quellen – wie Datenbanken oder Properties-Dateien stammen.
schnell + kompakt
55
5 – TestNG Annotations
5.6 Test Factories Test Factories lösen das Problem, wenn man Tests mehrfach mit anderen Parametern aufrufen möchte. Im vorigen Abschnitt, in dem die Annotation @Parameter vorgestellt wurde, konnte man in der XML-Datei definieren, welche Parameter übergeben werden sollen. Dies kann aber, bei einer langen Liste generierter Werte, recht problematisch sein. Auch das Setzen des invocationsCount eines Tests mit dem Versuch, die Werte im Test zu generieren, ist oft nicht möglich. Angenommen, ein Test möchte als Parameter die Werte „aaa“, „bbb“, „ccc“, u.s.w. bis „zzz“ bekommen. Dies möchte man nicht so in testng.xml schreiben: <parameter name="number-of-times" value="aaa"/>
Dieses Codestück müsste man dann in einen neuen Test eingebettet ziemlich oft wiederholen, was sehr lästig ist. Man könnte vielleicht einfacher einen DataProvider verwenden und alle Werte in das „return Object-Array“ schreiben, aber das ist ebenfalls lästig. Aus diesem Grunde ist es einfach möglich, Tests aus einer Factory dynamisch erstellen zu lassen und diese dann aufzurufen: public class IPTest { private String ip_address; public IPTest(String ip) { ip_address = ip; }
56
Test Factories
@Test public void testServer() { System.out.println("Ping on " + ip_address); } }
In diesem Beispiel gibt es eine Testklasse, die gerne Server testen und dazu alle IP-Adressen von 0 bis 255 aufrufen möchte. Ideal wäre es daher, wenn 256 Instanzen dieser Testklasse erstellt würden, bei denen alle das Feld ip_addresse immer auf alle richtigen Werte gesetzt wäre. Dies ermöglicht TestNG mit der Annotation @Factory, deren Methode einfach ein Array (Object[]) mit diesen Testklassen als Rückgabewert erwartet. Dies ist z.B. so möglich: public class IPTestFactory { String address=null; @Factory public Object[] createInstances() { Object[] result = new Object[256]; for (int i = 0; i < 256; i++) { address = "149.143.52." + ( new Integer(i).toString()); result[i] = new IPTest(address); } return result; } }
Wichtig ist hierbei, dass ein Object[]-Array erstellt wird, das die ganzen Instanzen von IPTest aufnehmen kann. Und diese ganzen Instanzen werden in der for-Schleife erstellt und bekommen über
schnell + kompakt
57
5 – TestNG Annotations
den Konstruktor die verschiedenen Werte (quasi zum Merken) gesetzt. TestNG nimmt dann das Array und ruft alle Testmethoden nacheinander auf. Der Output ist also: [testng] Ping on 149.143.52.0 [testng] Ping on 149.143.52.1 [testng] Ping on 149.143.52.2
Bleibt als Letztes die Frage, wie dies jetzt in der Datei testng.xml gestartet wird: <suite name="SMOKE">
Sie wird gestartet, indem die Factory-Klasse im Test angegeben wird. TestNG erkennt die Factory, holt sich das ObjectArray und führt alle enthaltenen Tests aus.
58
KAPITEL 6 Code-Coverage 6.1
Cobertura
63
6.2
Emma
66
6.3
Clover
68
Neben den Reports, die von TestNG, Maven und CruiseControl erzeugt werden, gibt es eine weitere Möglichkeit, seine Test-Suite darzustellen. Entwickelt man im Sinne des Test Driven Development, kommen irgendwann Fragen auf wie: Habe ich nach dem letzten Refactoring auch meine Tests nachgezogen? Habe ich den relevanten Code getestet und habe ich alle möglichen Verzweigungen meiner Businessmethode getestet? Die Antworten auf solche Fragen liefern Code-Coverage-Tools, welche die Abdeckung des Codes mit Tests darstellen. Eines der bekanntesten ist Clover. Es ist kommerziell, wird jedoch kostenlos an Open-Source-Projekte lizenziert. Ebenso einfach ist es möglich, freie Versionen für die Lehre an Universitäten zu erhalten. Neben Clover gibt es auch eine Reihe von leistungsfähigen Open-Source-Projekten, von denen hier stellvertretend Cobertura und Emma vorgestellt werden sollen. Code-Coverage ist ein sehr hilfreiches Werkzeug, sowohl für Entwickler als auch für die Projektleitung, das Management und den Vertrieb. Eine hohe Abdeckung des Codes durch Tests korreliert meistens mit hoher Qualität. Die Gefahr hierbei liegt jedoch
schnell + kompakt
59
6 – Code-Coverage
darin, dass man sich mehr auf die Abdeckung aller Klassen, Methoden und Verzweigungen mit Tests konzentriert als auf die Qualität der Testmethoden. Hinweis Man erhält z.B. bei einer Methode leicht 100% Abdeckung auch dann, wenn man mit nur einem Parameter testet. Wichtig ist es aber, alle relevanten Grenzwerte zu testen. Die Funktionsweise der Tools ist sehr ähnlich. Zunächst werden die zu testenden Klassen instrumentiert, also um Code erweitert, der es zulässt, Informationen über die Ausführung zu sammeln. Dabei gibt es drei verschiedene Varianten. Die Erweiterungen können sowohl auf Bytecode- oder Quellcode-Ebene als auch zur Laufzeit stattfinden. Sind diese Erweiterungen durchgeführt, wird während des Testlaufs überprüft, welcher Code ausgeführt wird. Diese Informationen werden währenddessen mitgeloggt. Es gibt verschiedene Arten der Informationen, die dabei gesammelt werden können. Es wird geprüft, ob Methoden während der Laufzeit angesprochen wurden, bestimmte Codezeilen ausgeführt worden sind und ob alle Verzweigungen abgedeckt wurden (Method-, Line-, Branch-Coverage). Anschließend wird aus dem Log und dem vorhandenen Quellcode ein Report generiert. Auch hier gibt es verschiedene Möglichkeiten. Alle Tools nutzen zumindest die Möglichkeit, den Report in Form von HTML zu erzeugen. Andere Formate bieten jedoch eine bessere Weiterverarbeitungsmöglichkeit. Welche Formate die unterschiedlichen Tools bieten und ob alle der oben genannten Arten der Coverage-Analyse angeboten werden, wird in den folgenden Abschnitten zum jeweiligen Tool beschrieben.
60
Code-Coverage
Die Integration der Tools in TestNG ist aufgrund dessen, dass sie nicht auf einen bestimmten Output des Testlaufs angewiesen sind, sehr einfach möglich. Um die Integration mit TestNG zu zeigen, beschränken wir uns bei den hier vorgestellten Tools auf Ant. Die Einbettung in Maven oder die Ausführung via IDE-Plug-in ist dann durch Abstraktion leicht nachvollziehbar. Nachdem das jeweilige Tool heruntergeladen wurde (cobertura.sourceforge.net bzw. emma.sourceforge.net und Clover auf cenqua.com), müssen die Libraries im Classpath verfügbar gemacht werden. In den folgenden Beispielen wird davon ausgegangen, dass alle benötigten .jar-Dateien im Ordner ${libdir} gespeichert sind. Um nicht bei allen Code-Beispielen erneut die Definition der Zielpfade und Variablen angeben zu müssen, soll hier der Inhalt der .properties-Datei mit den für alle Coverage-Tasks gleichbleibenden Werten dargestellt werden. libdir=${basedir}/libs src=${basedir}/src/java testsrc=${basedir}/src/test build=${basedir}/target class.dir=${build}/classes test.classes=${build}/test suitedir=${testsrc}/conf testng.report.dir=target/report cov.dir=${basedir}/coverage cov.inst.dir=${cov.dir}/cov-instr cov.rep=${cov.dir}/cov-rep cov.rep.html=${cov.rep}/html cobdata=${cov.dir}/cobertura.ser clovdata=${cov.dir}/mycoverage.db
schnell + kompakt
61
6 – Code-Coverage
Geladen werden diese Angaben wie im folgenden Listing: <project name="TestNGBook" default="test" basedir=".">
Für die weitere Verwendung werden noch einige Klassenpfade definiert, auf die über deren id zugegriffen werden kann. <path id="cp"> <path id="classes"> <path id="testclasses"> <path id="covpath"> <path refid="cp" /> <pathelement location="${test.classes}" /> <pathelement location="${cov.inst.dir}" />
Für die Verwendung muss natürlich auch der testng-Task definiert werden.
62
Cobertura
Diese Einstellungen gelten für alle Code-Coverage-Tools. Die weiteren Listings sind bei allen Tools individuell.
6.1 Cobertura Eines der derzeit beliebtesten Code-Coverage-Werkzeuge ist Cobertura. Es steht unter der GPL und ist somit frei verfügbar. Als Erweiterung des Projekts jcoverage bietet es einen großen Leistungsumfang. Die HTML-Reports sind durch ein wenig JavaScript aufgewertet und erlauben so z.B. Sortierungen. Cobertura unterstützt alle drei Formen der Coverage-Analyse (Line, Method, Branch) und es ist voll kompatibel zu TestNG und JUnit. Zusätzlich wird eine Angabe über die Komplexität der Klasse ausgegeben, welche direkt mit der Anzahl der Verzweigungen innerhalb der Klasse zusammenhängt. Eine weitere Eigenschaft ist die Ermittlung von Zyklen nach der McCabe-Metrik. Hinweis Um in den Coverage-Tools auf Zeilennummern zugreifen zu können, muss der Code mit der Option debug=“true“ kompiliert werden. Zunächst benötigt man, um den Cobertura-Task zu definieren, eine Properties-Datei aus dem cobertura.jar.
Anschließend können die Cobertura-Tasks ausgeführt werden, um die Coverage-Analyse durchzuführen. Zuerst muss der bereits kompilierte Code instrumentiert werden, der dazu notwendige compile-Task wird hier nicht mit angegeben und lässt sich mithilfe der Ant-Dokumentation leicht erstellen:
schnell + kompakt
63
6 – Code-Coverage
Anschließend kann der testng-Task benutzt werden, um den instrumentierten Code zu testen. Bei diesem Schritt werden die Coverage-Informationen in die cobertura.ser-Datei (${cobdata}) geschrieben. <sysproperty key="net.sourceforge.cobertura.datafile" file="${cobdata}" /> <xmlfileset dir="${suitedir}" includes="testng.xml" />
Eine Besonderheit für Cobertura ist das Setzen der Umgebungsvariable durch <sysproperty>. Damit kann Cobertura zur Laufzeit auf die .ser-Datei zugreifen. Nachdem die Tests nun auf den instrumentierten Klassen ausgeführt und die Laufzeitinformationen dabei erzeugt wurden, kann der Cobertura-Report erstellt werden. Das folgende Beispiel zeigt dies für den HTML-Report. Eine weitere Möglichkeit ist ein Report im XML-Format, der beliebig weiterverarbeitet werden kann.
64
Cobertura
Der Report liegt anschließend im ${cov.rep.html}-Verzeichnis und entspricht, je nach Code, den folgenden Abbildungen:
Abb. 6.1: Package-Übersicht des Cobertura-Reports
Aus der Übersichtsdarstellung gelangt man leicht zu den einzelnen Klassen. Dort erkennt man, welcher Code (grün) wie oft (die Zahl neben der Zeilennummer) ausgeführt wurde. Nicht ausgeführter Code wird rot dargestellt.
Abb. 6.2: Cobertura-Coverage einer Klasse
schnell + kompakt
65
6 – Code-Coverage
6.2 Emma Emma war eines der ersten freien Code-Coverage-Tools und ist immer noch sehr beliebt. Es ist klein, schnell und liefert einen sehr guten HTML-Report. Emma ist integraler Bestandteil des JTiger-Test-Framework (vgl. Kapitel 10). Es unterstützt die Metriken für Class-, Method-, Block- und LineCoverage. Man kann anhand der Blöcke auch darauf schließen, ob Verzweigungen ausgeführt wurden. Die Integration in TestNG ist ebenso einfach wie bei Cobertura. Nachdem die allgemeinen Parameter gesetzt sind, wird der Emma-Task definiert:
Anschließend können die Emma-Tasks verwendet werden. Zunächst wird der Bytecode, also die bereits kompilierten Klassen, wie folgt instrumentiert: <emma>
Die instrumentierte Version der Klassen wird dann in ${destdir} geschrieben und ein metadatafile wird angelegt.
66
Emma
Anschließend muss der Test ausgeführt werden, um die Coverage-Daten zu sammeln. Die Definition des testng-Task unterscheidet sich nur in einigen Parametern von der Cobertura-Variante, wird aber entsprechend im folgenden Listing komplett dargestellt: <xmlfileset ...analog zu cobertura.../> <jvmarg value="-Demma .coverage.out.file=${cov.dir}/ metadata.emma"/> <jvmarg value="-Demma .coverage.out.merge=true"/>
Nun sind alle Informationen für Emma verfügbar, um den Report zu generieren. <emma verbosity="verbose">
Neben der Möglichkeit, HTML zu erstellen, kann auch eine einfache Textdatei, ebenso wie eine XML-Datei, generiert werden. Die folgende Abbildung zeigt einen typischen Emma-Report:
schnell + kompakt
67
6 – Code-Coverage
Abb. 6.3: Emma-Report
6.3 Clover Clover ist wohl das am weitesten entwickelte Code-CoverageWerkzeug. Es stammt von der Firma Cenqua und steht nicht unter Open-Source-Lizenz. Clover integriert sich hervorragend in das ebenfalls von Cenqua bereitgestellte Tool FishEye, eine WebApplikation zur Verwaltung von Quellcode-Repositories, Bugs/ Issues und erzeugt Statistiken für das verwaltete Projekt. Beide Tools sind für Open-Source-Projekte kostenlos und bieten neben hervorragender Dokumentation auch eine wesentlich höhere Übersichtlichkeit für alle Beteiligten. Clover ist auch nicht ausschließlich auf Java beschränkt, sondern steht ebenso für die .NET-Plattform zur Verfügung.
68
Clover
Es bietet alle Arten der Coverage-Metriken und der HTML-Report ist noch ein kleines Stück besser als der von Cobertura. Zusätzlich ist Clover in der Lage, „Historical Reports“ zu erstellen. Diese werden als Kurve ausgegeben und zeigen, wie sich die Abdeckung während der Zeit der Entwicklung geändert hat. Das kann besonders hilfreich sein, wenn man bemüht ist, eine ständig hohe Abdeckung mit Tests zu erreichen. Neben den üblichen Reports (HTML, XML) gibt es auch die Möglichkeit, PDF-Reports zu erzeugen (Summary-Report). Nicht jede Form des Reports ist mit jeder Formatierung möglich. Um Clover innerhalb von Ant nutzen zu können, müssen zwei .jar-Dateien und die Lizenzdatei bereitgestellt werden. Wenn alle drei Dateien im ${libdir}-Verzeichnis liegen, kann der Task wie folgt definiert werden. <extendclasspath path="${libdir}/clover.jar" />
Alle Clover-Tasks sind abhängig vom Target with.clover, welches zuerst ausgeführt werden muss. Es beinhaltet den initString, welcher den Pfad zur Clover-Datenbankdatei angibt.
schnell + kompakt
69
6 – Code-Coverage
Anschließend wird das TestNG-Target definiert. Dabei muss der Pfad zum Quellcode angegeben werden, da Clover diesen instrumentiert, statt den Bytecode zu erweitern. <pathelement path="${class.dir}"/> <xmlfileset...analog zu cobertura.../>
Wenn der Testlauf beendet ist, kann aus den Informationen, die Clover daraus erhält, ein Report erstellt werden. Die HTML-Variante ist im folgenden Beispiel dargestellt: <current outfile="${cov.rep.html}">
Der von Clover generierte Report zeigt die Gesamtabdeckung des Package, aller Klassen, der Methoden und Statements an. Zeigt man sich eine Klasse separat an, so bekommt man auch die Information, wie oft ein bestimmtes Stück Code durchlaufen wurde. Diese Information kann auch hilfreich sein, falls man Zyklen und Ähnliches erkennen will. Die folgende Abbildung zeigt einen solchen HTML-Report.
70
Clover
Abb. 6.4: Übersichtsdarstellung der Code-Coverage mit Clover
Die Darstellung des Codes entspricht weitestgehend der Darstellung, die Cobertura bietet.
schnell + kompakt
71
KAPITEL 7 Automatisierung und Buildtools 7.1
Automatisierte Tests
74
7.2
Ant
76
7.3
Maven
82
7.4
CruiseControl
92
In Software-Projekten wird nunmehr fast ausschließlich CodeManagement-Software wie CVS, Subversion oder Ähnliches eingesetzt. Der große Vorteil dieser Systeme ist es, dass mehrere Entwickler an ein und derselben Codebasis arbeiten können. Konflikte bei konkurrierenden Änderungen mehrerer Entwickler werden sofort entdeckt und können sogar mithilfe der Versionierungssoftware behoben werden. Unterstützt werden Vergleichsansichten, die die Möglichkeit geben, den Code zu vereinen (Diff/Merge). Die einheitliche Codebasis bildet aber auch die Grundlage für automatisierte Builds, Tests und Deployments. Dies alles führt zu einer gleich bleibend hohen Codequalität, da automatisierte Prozesse die Anzahl der möglichen, individuellen Fehler verringern. So ist es schon während der Entwicklung möglich, zu überprüfen, ob die gerade neu eingecheckten Features, Bugfixes oder auch Komponenten im Zusammenspiel mit der bereits bestehenden Software funktionieren und somit die Integration fehlerfrei durchgeführt werden konnte. Die Automatisierung dieser Faktoren und das ständige Überprüfen des Entwicklungsprozesses werden als Continuous Integra-
schnell + kompakt
73
7 – Automatisierung und Buildtools
tion bezeichnet. Die Quelle, die im Zusammenhang mit diesem Prozess stets genannt wird, ist der Artikel von Martin Fowler: http://www.martinfowler.com/articles/continuousIntegration.html Er beschreibt sehr detailliert, wie Entwicklerteams diese Technik umsetzen können. Einer der Schlüsselfaktoren in diesem Zusammenhang ist das automatisierte Testen, welches in den BuildProzess integriert wird. Im folgenden Abschnitt wird die Verwendung von TestNG innerhalb von automatisierten Build-Prozessen beschrieben. Eines der beliebtesten Tools, um Continuous Integration umzusetzen, ist CruiseControl (cruisecontrol.sourceforge.net). Dieses Kapitel behandelt auch, wie CruiseControl und TestNG zusammenarbeiten.
7.1 Automatisierte Tests und Buildtools Die meisten Entwickler benutzen integrierte Entwicklungsumgebungen, um grafische Debugger nutzen zu können und mehr Komfort bei der Entwicklung zu haben. Testläufe, Compiler-Einstellungen, Pfade zu Ressourcen und Bibliotheken sind jedoch meist sehr genau auf die IDE und die Entwicklungsumgebung angepasst. Jeder entwickelt in der für ihn idealen Umgebung. Automatisierung, Integration und Integrationstests sind dadurch jedoch schwer handhabbar. Der Einsatz von Build-Skripten, die von allen Entwicklern gleichermaßen genutzt werden, ermöglicht es, Projekte „Out of the Box“ in einer neuen Entwicklungsumgebung lauffähig zu machen, und erleichtert Integration und Tests. Auf diese Weise können Tests automatisiert im Zyklus der Entwicklung ausgeführt werden. Reports der Testergebnisse werden erstellt und führen in Abhängigkeit von bestimmten Ergebnissen zu einem neuen Build oder melden die Fehler dem (wahrscheinlichen) Verursacher.
74
Automatisierte Tests und Buildtools
In agilen Software-Projekten wird häufig verlangt, dass Tests ständig, oder zumindest über Nacht, laufen. Dazu wird der Code aus dem Repository (Subversion, CVS etc.) entnommen, kompiliert, und die Tests werden ausgeführt. Die Tests, die jeder Entwickler auf seiner Maschine geschrieben hat, um seinen Code zu testen, werden auf einem dedizierten Rechner zusammen mit den Tests der anderen Entwickler und gegebenenfalls umfassenderen Integrationstests ausgeführt. Diese Tests vermitteln ein Gefühl der Sicherheit, da die Software so funktioniert, wie die Entwickler es geplant haben. Tests auf höherer Ebene, z.B. durch die QA-Abteilung ersetzen solche Systeme jedoch nicht, da unterschiedliche Auffassungen in Bezug auf die Anforderungen zum Alltag gehören. Je komplexer eine Anwendung ist, je mehr Software-Systeme (Application Server, Datenbanken, Web Services) eingebunden sind, desto aufwändiger wird die Konfiguration automatischer Testläufe. Da sich dieses Buch mit Unit-Tests von Anwendungen beschäftigt, wird davon ausgegangen, dass das Kompilieren der zu testenden Anwendung erfolgreich war und alle benötigten Systeme zum Testzeitpunkt zur Verfügung stehen. Bei komplexen Umgebungen werden oftmals Mock-Objekte verwendet, um lokal testen zu können. Dies erlaubt auch ein Design by Contract, also das Entwickeln gegen eine definierte Schnittstelle, auch wenn diese nicht direkt zur Verfügung steht. In einem automatisierten Test ist es nun möglich, unter „realen“ Bedingungen zu testen, also die Mock-Objekte durch die entsprechenden Business-Klassen zu ersetzen. Der Schlüssel zu einer solchen Automatisierung sind Buildtools, die im folgenden Abschnitt vorgestellt werden. Die derzeit wohl flexibelste Einbindung von TestNG in ein JavaProjekt erfolgt über Build- und Projektmanagement-Tools wie Ant und Maven. Die dafür benötigten Tasks bzw. Plug-ins wer-
schnell + kompakt
75
7 – Automatisierung und Buildtools
den von TestNG zur Verfügung gestellt. Eine solche Integration erlaubt das regelmäßige Ausführen der Tests manuell oder automatisiert. In vielen Java-Projekten wird generell auf ein Buildtool gesetzt, um die verschiedenen Ressourcen zusammenzustellen, Abhängigkeiten aufzulösen und Archive für das Deployment zu erstellen. Die Integration eines Test-Framework ist hier besonders dann sinnvoll, wenn viele Entwickler beteiligt sind, die selten gemeinsam (d.h. persönlich anwesend) testen können, oder wenn die Tests auf einem Integrationsserver durchgeführt werden. Eine weitere Möglichkeit ist die Einbettung in integrierte Entwicklungsumgebungen wie Eclipse oder Intellij IDEA. Sie erlauben es, lokal Tests auszuführen und die eigenen Methoden auf Korrektheit und Vollständigkeit zu überprüfen. Eine solche Integration bietet viel Komfort für den Entwickler und unterstützt vor allem Unit-Tests während der Entwicklung.
7.2 Ant Als Standard-Buildtool hat sich Ant bewährt. Es ermöglicht über die Konfiguration via XML die Abbildung komplexer Strukturen und eine äußerst flexible Anpassung des Build-, Test- und Deploy-Prozesses an Umgebung, Unternehmensstruktur und die Anwendungsdomäne. Die Integration von TestNG in Ant ist denkbar einfach und auf der TestNG-Homepage gut dokumentiert. Nach dem Bereitstellen des benötigten jar-Archivs und der Einbettung in den Classpath muss der testng-Task definiert werden:
Anschließend kann man mit Ant alle TestNG-Tests ausführen.
76
Ant
Dabei gibt es verschiedene Möglichkeiten, wie definiert wird, welche Testfälle ausgeführt werden sollen. Eine Möglichkeit ist es, eine testng.xml-Datei zu verwenden (vgl. Kapitel 4). Diese Möglichkeit sollte als Königsweg betrachtet werden, da alle Plug-ins und Tools diese Möglichkeit nutzen. Die Abstraktion vom verwendeten Tool ist so also gegeben und der Wechsel zwischen Ant, Maven oder einem IDE-Plug-in gelingt ohne weitere Konfiguration der Tests. Als Quickstart soll im folgenden Beispiel gezeigt werden, wie man mit Ant die Tests ausführt, die in einer testng.xml-Datei bereits angegeben sind. Im Anschluss an die Task-Definition wird auf die verwendeten Attribute eingegangen: <xmlfileset dir="${suitedir}" includes="testng.xml"/>
Vorerst sind hier nur die wichtigsten Einstellungen des testngTask aufgezeigt. Der classpathref-Eintrag verweist auf eine FileSet-Struktur, die in Ant üblich ist. Sie beinhaltet sowohl die Tests als auch die zu testenden Klassen sowie die verwendeten Libraries. Das Attribut verbose bezeichnet den Log-Level von TestNG und gibt somit mehr oder weniger Auskunft über den Testlauf bei dessen Ausführung. Das outputDir legt fest, wohin die Reports von TestNG gespeichert werden sollen. Das sourcedir beschreibt den Ort, an dem der Test-Code zu finden ist. Und das eingebettete xmlfileset bestimmt den Ort, an dem die Testdefinition liegt. Dieser Tag kann auch erweitert werden, um mehrere testng.xml-Dateien aufzunehmen, und kann zur Organisation von verschiedenen Testszenarien von Vorteil sein.
schnell + kompakt
77
7 – Automatisierung und Buildtools
Als Resultat des Testlaufs werden in der Konsole, je nach LogLevel, Informationen über geglückte und fehlgeschlagene Tests angegeben. Zusätzlich wird dazu eine HTML-Dokumentation im Verzeichnis ${testng.report.dir} erstellt, die einen guten Überblick liefert. Alle fehlgeschlagenen Tests (Methoden) werden in der Datei testng-failed.xml gesammelt. Diese entspricht einer Standard-testng.xml-Datei, in der nur die letzten fehlgeschlagenen Tests eingetragen wurden. Durch ihre Angabe für die Ausführung des Testlaufs werden nur die fehlgeschlagenen Tests erneut ausgeführt. Bei langen Tests, die mehrere Minuten oder Stunden dauern, kann dies für Zeitersparnis beim Beseitigen des Fehlers sorgen. Ein derart konfigurierter Task reicht vielfach für die Ausführung, auch von anspruchsvollen Tests. Es gibt jedoch beim Ant-Task weitere Optionen, die im Folgenden vorgestellt werden sollen. Neben der Möglichkeit, Tests mittels testng.xml zu definieren, ist Ant ebenso in der Lage, diese direkt auszuführen. Die XML-Datei bietet zwar alle verfügbaren Möglichkeiten, jedoch ist eine Definitionsmöglichkeit innerhalb von Ant gerade dann besonders interessant, wenn Skriptsprachen zur dynamischen Ausführung von Tests eingesetzt werden sollen. Neben einer Verwendung von Skriptsprachen außerhalb kann ebenso auf die Integration von BeanShell innerhalb von TestNG zurückgegriffen werden. Diese Skripte werden in der testng.xmlDatei definiert und erlauben flexible Zugriffe auf den Testlauf. Dadurch ist das Ausführen von Tests unter bestimmten Umständen wie Wochentag oder Tageszeit möglich. Zur Verwendung sei hier, ausnahmsweise, auf die Dokumentation verwiesen. Um Tests direkt im Ant-Task zu definieren, wird wie zuvor gesehen ein testng-Task eingesetzt. Dieser enthält jedoch statt des eingebetteten Parameters xmlfileset Attribute zur Konfigura-
78
Ant
tion der auszuführenden Tests. Eingebettet wird ein classfileset, welches die zu verwendenden Testklassen beinhaltet. Der im folgenden Listing dargestellte Task führt alle Testgruppen aus, die auf „RC1“ enden. Dabei werden jedoch jene ausgelassen, die als BROKEN gekennzeichnet sind.
Es ist übrigens weder möglich, ein classfileset und ein xmlfilesetdir gemeinsam zu nutzen, noch mehrere classfilesets anzugeben. Die Angabe des Classpath und das Inkludieren/Exkludieren von Dateien nach Namensmuster erlaubt ohnehin das Erreichen aller benötigten Klassen. Neben den beiden Möglichkeiten, die bereits für den testng-Task dargestellt wurden, können noch zwei Parameter eingebettet werden. Zum einen ist das ein Kommandozeilen-Parameter, der übergeben werden kann. Dies ist im Folgenden dargestellt: <jvmarg value="-Djdbc.drivers=org.hsql.jdbcDriver" />
Damit ist es möglich, verschiedene Pfade für die Anwendung zu setzen oder Compiler-Einstellungen vorzunehmen. Eine andere Möglichkeit ist das Setzen einer Umgebungsvariable innerhalb des testng-Task. Diese gilt während der Ausführung der Tests und überschreibt in dieser Zeit die systemweit gesetzten Werte. So ist es möglich, für den Test ein gesondertes Home-
schnell + kompakt
79
7 – Automatisierung und Buildtools
Verzeichnis anzugeben, in dem z.B. bestimmte Properties-Dateien, Datenbankschemata oder Ähnliches liegen. Auch die explizite Angabe eines JAVA_HOME ist hier möglich. <sysproperty key="usrdir" value="${usrdir}"/>
Der testng-Task selbst hat neben den bereits vorgestellten Attributen noch weitere, die im Folgenden dargestellt werden. Durch das Attribut dumpCommand des testng-Task in Ant wird der Kommandozeilenaufruf von TestNG bei dessen Ausführung ausgegeben. Dieses Attribut kann sehr hilfreich sein, gerade bei Fehlern während der Ausführung, wie z.B. ausgelassene Klassen. Auch ermöglicht es, diese Kommandos direkt und ohne Ant in der Kommandozeile einzusetzen. Das folgende Listing zeigt das Attribut und dessen Ausgabe während der Ausführung mit Ant: test: [testng] C:\jdk1.5.0_06\jre\bin\java.exe [testng] -ea [testng] -classpath [testng] E:\target\classes;E:\target\test [testng] org.testng.TestNG [testng] -log [testng] 8 [testng] -d [testng] E:\workspace\TestNGBook\target\report [testng] -target [testng] 1.5 [testng] -sourcedir [testng] E:\workspace\TestNGBook\src\test
80
Ant
Die Attribute holdonfailure und holdonskipped werden gesetzt, um den Testlauf abzubrechen, falls ein Test fehlschlägt oder ausgelassen werden muss. Analog dazu können Properties durch skippedProperty und failureProperty gesetzt werden. Diese Attribute sind jedoch nur verfügbar, wenn holdonfailure bzw. holdonskipped nicht gesetzt wurden. Sie ermöglichen es, dynamisch im Build-Prozess auf Unregelmäßigkeiten zu reagieren. Ein besonders wichtiges Attribut ist das workingdir. Es erlaubt es, die Ausführung der Tests auf ein bestimmtes Verzeichnis zu setzen, z.B. in den Build-Ordner, in dem auch alle Ressourcen liegen, die dann relativ adressierbar sind. Durch das Attribut testjar ist es möglich, alle Tests in einer .jarDatei auszuliefern und diese zu nutzen, um die Tests auszuführen. Diese .jar-Datei muss eine testng.xml-Suite beinhalten. Ein weiteres sehr hilfreiches Attribut ist timeout. Es definiert die maximale Ausführungsdauer aller Tests. Sollten die Tests nicht in der Zeit ablaufen, bricht der Test mit Fehler ab. Dieses Attribut ist ab Version 5 von TestNG verfügbar und verhindert z.B., dass der Buildserver in eine Endlosschleife gerät. Will man noch flexibler auf Testläufe reagieren, ist dies sehr einfach möglich, indem man die Klasse org.testng.ITestListener implementiert. Die Methoden, die das Interface deklariert, sind selbst erklärend (z.B. onTestStart, onTestFailure …). Was genau das Programm beim Eintreffen der Events macht, liegt im eigenen Ermessen. Möglich wäre es z.B., eine Nachricht an einen Instant Messenger oder eine E-Mail zu verschicken. Um die Ergebnisse des Testlaufs an diese Klasse weiterzuleiten, dient das Attribut reporter, welches den voll qualifizierten Klassennamen übergeben bekommt. TestNG zeichnet sich besonders dadurch aus, dass es JUnit-Tests ausführen kann, wodurch der Umstieg erleichtert wird. Dazu
schnell + kompakt
81
7 – Automatisierung und Buildtools
übergibt man dem testng-Task in Ant das Attribut junit. Natürlich müssen dann auch JUnit-Tests im Classpath vorhanden sein. In der Version 5.2 wurde dieses Feature noch einmal erheblich verbessert. So werden JUnit-Tests nicht mehr durch TestNG emuliert, sondern es wird ein JUnit-Kern direkt benutzt, um die Tests auszuführen. Dabei wird der Output von JUnit in TestNG verwendet. Durch diese Möglichkeit ist nun eine sehr hohe Kompatibilität gewährleistet und sehr viele JUnit-Erweiterungen werden direkt in TestNG nutzbar. Um die Migration von JUnit noch weiter zu erleichtern, ist es auch möglich, einen JUnit-Report zu erstellen. Dazu benutzt man den folgenden Task, dessen Ergebnis in Abbildung 7.1 im Maven-Kapitel zu sehen ist: <junitreport todir="test-report">
7.3 Maven Maven benutzt eine etwas andere Herangehensweise als Ant. So wird in Maven nicht versucht, die Struktur eines Projekts so flexibel wie möglich gestaltbar zu machen, sondern – zumindest in der Grundkonfiguration – eine sehr gute Struktur vorzugeben. In der aktuellen Version 2 erlaubt es Maven auch, alle Abhängigkeiten zu anderen Bibliotheken aufzulösen. Nicht zuletzt deshalb bezeichnet sich Maven als Projektmanagement-Tool. Maven Version 1.x Die Integration in das Projektmanagement-Tool Maven verläuft ebenso einfach wie die Einbettung in Ant.
82
Maven
Zunächst installiert man Maven 1, indem man es herunterlädt und an der gewünschten Stelle entpackt. Zu empfehlen ist, wie bei allen Java-Programmen, ein Pfad ohne Leer- und Sonderzeichen. Nun legt man die Umgebungsvariable MAVEN_HOME an und erweitert den Pfad für die Ausführung. Dazu benutzt man unter Windows den Befehl: set MAVEN_HOME=C:\Maven set PATH=%MAVEN_HOME%\bin;%PATH%
Analog zur Installation unter Windows benutzt man den exportBefehl unter Unix/Mac OS X, um die Eintragungen vorzunehmen. Überprüft wird die Installation durch die Eingabe #$>maven -v Danach lädt man das Repository für die Standard-Maven-Funktionen. Hier liegen alle Bibliotheken, die Maven benötigt, um Abhängigkeiten zwischen Plug-ins und Projekten aufzulösen. install_repo.bat %USERPROFILE%\.maven\ repository
Die lokale Kopie des Repository für Maven liegt nun im HomeVerzeichnis. Danach ist Maven installiert und ein Projekt kann gestartet werden. Dazu legt man den Ordner an, in dem sich das Projekt befinden soll und benutzt den Befehl #$>maven genapp, der das genappPlug-in ausführt. Im Wurzelverzeichnis befindet sich ein src-Ordner, der die Konfigurations-, Source- und Test-Files aufnimmt. Weiterhin liegen dort die Dateien project.properties und project.xml. Die Vorbereitungen für das erste Maven-Projekt sind damit abgeschlossen und die Integration von TestNG kann vorgenommen werden. Installiert wird das TestNG-Goal über das Remote-Repository von vanwardtechnologies.com. Dieses trägt man wie folgt in der Datei project.properties ein.
schnell + kompakt
83
7 – Automatisierung und Buildtools
# adding remote repository maven.repo.remote=http://www.ibiblio.org/maven,http:// www.vanwardtechnologies.com/repository/
Sollen die Einstellungen für alle Projekte gelten, so ist das Repository in der Datei ${user.home}/build.properties einzutragen. Die Installation ist auch über das Installationsskript möglich: #$>install_repo.bat www.vanwardtechnologies .com/ repository/
Ist die Installation geglückt, liegt im lokalen Repository ein neuer Ordner der angegebenen Seite. Anschließend wird das TestNG-Plug-in installiert und die Fragen werden wie folgt beantwortet: #$>maven plugin:download ? artifactId: maven-testng-plugin ? groupId: testng ? version: 1.2
Nach der Installation befindet sich im Cache-Verzeichnis von Maven das Plug-in ${user.home}/.maven/cache/maven-testng-plugin-1.2. Um TestNG zu nutzen, kann es nötig sein, an dieser Stelle in der Datei plugin.properties die Pfade zu suitexml.dir und source.dir anzupassen. Sollen die Einstellungen nur projektspezifisch gelten, ist die Datei project.properties um folgende Einträge zu erweitern: maven.testng.suitexml.dir=${basedir}/pfad/ testng.xml maven.testng.source.dir=${basedir}/src/test/
84
Maven
Um nun Tests auszuführen, benötigt man, wie oben erkennbar, eine testng.xml, die den Testlauf regelt. Darin werden wie üblich Classes, Packages, Methods und/oder Groups definiert. Der Aufruf #$>maven testng erstellt automatisch unter dem target-Ordner den testng-output-Ordner. Darin befindet sich der Report des Testlaufs im HTML-Format. Wie bei Ant gibt es auch bei Maven die Möglichkeit, einen Report im Stil von JUnit zu erzeugen. Dazu steht das Goal #$>maven testng:junit-report zur Verfügung. Das Ergebnis liegt dann im Ordner target\testngJunitReport. Die folgende Abbildung zeigt einen solchen Report.
Abb. 7.1: JUnit-Style-Report von TestNG
schnell + kompakt
85
7 – Automatisierung und Buildtools
Maven Version 2.x In Maven 2 wurde einiges, im Vergleich zur Version 1, verändert. Das Konzept Konvention über Konfiguration wurde strikt weiterentwickelt. Projekte in Maven basieren jetzt auf dem Project Object Model (POM). Sie haben eine Standardverzeichnisstruktur, deren Anpassung zwar möglich, aber nicht empfohlen ist. Diese Struktur findet sich in allen Maven-Projekten wieder und erleichtert somit den Einstieg. Projekte haben nun einen Lifecycle, der aus mehreren Phasen besteht: vom Validieren, Kompilieren über das Testen bis hin zum Deployment. Da die einzelnen Phasen des Lebenszyklus voneinander abhängen, führt die Ausführung eines Goal, z.B. test, dazu, dass auch die Phasen Validate und Compile ausgeführt werden. Ein Umstieg auf Maven 2 lohnt sich. In der neuen Version wurde auf die Integration von Ant und die Skriptsprache Jelly verzichtet, wodurch Maven erheblich schneller geworden ist. Plug-ins werden in Java entwickelt, es gibt aber auch Erweiterungen für Skriptsprachen, die nicht zum offiziellen Maven-Projekt gehören. Aber vor allem das automatische Auflösen von Abhängigkeiten zwischen Projekten (Dependency Management) ist eine Spezialität von Maven 2. So kann es verhindern, dass Libraries mehrfach benutzt bzw. im Classpath referenziert werden (fast jedes OpenSource-Tool bringt z.B. eine Log4J-Version mit). Jede Library definiert genau, welche zusätzlichen Abhängigkeiten bestehen. Wenn diese bereits vorhanden sind, werden sie mit genutzt, ansonsten automatisch aus dem Internet oder einem vorhandenen Maven-Proxy geladen. Das erspart sehr viel Konfigurationsarbeit und vereinfacht die Auslieferung der Anwendung.
86
Maven
Durch die Menge an Test-Frameworks, die in den letzten Jahren entstanden sind, wurde eine einheitliche Schnittstelle für deren Integration entwickelt: surefire. An dieser Schnittstelle setzt auch die Integration von TestNG an. Der kürzlich stattgefundene Versionssprung von TestNG 4.x auf 5.x macht es notwendig, ein weiteres Repository einzubinden, worauf im Folgenden noch eingegangen wird. Das Repository wird von einem Mitglied der TestNG-Community gepflegt, um die Funktionalität der Surefire-Schnittstelle für TestNG zu erweitern. Beim Verfassen dieses Buchs ist die aktuellste Version für die TestNG-Integration 5.1. Gerade während der Einführung wurden häufiger Updates des Repository vorgenommen. Die Installation von Maven 2 verläuft ähnlich wie bei Maven 1: herunterladen, entpacken und die Pfade eintragen (siehe Maven 1.x). Anschließend kann man die Installation durch #$>mvn –version überprüfen. Bei der ersten Ausführung eines Goal in einem Projekt werden alle benötigten Abhängigkeiten aus dem Internet geladen. Gehen wir davon aus, dass noch kein Maven-2-Projekt existiert, dann ist der einfachste Weg, ein solches mithilfe des QuickstartArchetype anzulegen. Dazu benutzt man, angepasst an das eigene Projekt, folgendes Kommando: mvn archetype:create -DgroupId=de.testbook -DartifactId=chapter09
Sind diese Vorbereitungen erledigt, können Code und Tests entwickelt werden. Gehen wir davon aus, dass diese bereits vorhanden sind. Der wichtigste Teil ist nun die Konfiguration des Projekts für die Nutzung von TestNG in Maven. Dazu muss die Datei pom.xml im Wurzelverzeichnis des Projekts bearbeitet werden.
schnell + kompakt
87
7 – Automatisierung und Buildtools
Zunächst muss das Repository angegeben werden, aus dem die aktuelle TestNG-Version für Maven 2.0 geladen werden kann. Dazu muss auch das Snapshot-Repository von Maven eingebunden werden, solange die Integration noch im Beta-Stadium ist. Durch derzeit häufige Änderungen an der Surefire-Schnittstelle müssen auch beim TestNG-Plug-in ständig Änderungen durchgeführt werden. Maven lädt alle Änderungen jedoch automatisch aus dem Repository. tapestry.javaforge http://howardlewisship.com/repository apache.snapshots Maven Central Plugins Development Repository http://cvs.apache.org/maven-snapshotrepository <snapshots> <enabled>true <enabled>false
88
Maven
Für TestNG muss neben dem Repository auch das eigentliche TestNG-Plug-in eingetragen werden: <dependencies> <dependency> org.testng <artifactId>testng 5.1 <scope>compile jdk15
Als Nächstes wird das Build-Goal konfiguriert. Wichtig sind dabei das surefire- und das compiler-Plug-in: <defaultGoal>package <sourceDirectory>src/main/java src/test/java org.apache.maven.plugins <artifactId>maven-compiler-plugin <source>1.5 1.5
schnell + kompakt
89
7 – Automatisierung und Buildtools
org.apache.maven.plugins <artifactId>maven-surefire-plugin 2.8-SNAPSHOT <suiteXmlFiles> <suiteXmlFile>src/test/conf/testng.xml
Zunächst wird konfiguriert, wo sich die Quellen und die Testdateien befinden. Das compiler-Plug-in muss für die Verwendung von Java 5 ebenfalls eingestellt werden. Das surefire-Plug-in, zuständig für die TestNG-Einbettung, benötigt die oben angegebene Version, die aus dem Repository von howardlewisship.com bezogen wird. Die Angabe der testng.xml-Datei erlaubt die Verwendung einer Testkonfiguration innerhalb von Maven und in anderen Tools, wie z.B. einer IDE. Diese Angabe ist optional, falls alle Tests ausgeführt werden sollen. Um das site-Goal so zu nutzen, dass die surefire-Reports angezeigt werden, muss der Abschnitt hinzugefügt werden. Die Verlinkung des Quellcodes innerhalb der von Maven generierten Projekt-Webseite geschieht über das jxr-Plug-in.
90
Maven
org.apache.maven.plugins <artifactId>maven-surefire-report-plugin true org.apache.maven.plugins <artifactId>maven-jxr-plugin
Das Plug-in erzeugt beim Ausführen des site-Goal einen TestNGReport unter Project Reports/Maven Surefire Report, der in Abbildung 7.2 dargestellt ist. Hinweis Leider ist derzeit bei Verwendung einer Test-Suite der Report nicht korrekt formatiert. Anstelle der Verlinkung der Testklassen und Packages zum Code und den Javadoc-Seiten wird der Name der Suite verlinkt. Dieser Link führt dann zu keiner Seite. Das Problem ist bereits bekannt und an einem Patch wird gearbeitet. Wird keine Test-Suite verwendet, so werden alle Tests ausgeführt und der Report ist fehlerfrei.
schnell + kompakt
91
7 – Automatisierung und Buildtools
Abb. 7.2: Maven-generierte Seite mit TestNG-Ergebnis
7.4 CruiseControl CruiseControl ist ein Werkzeug, mit dem sehr komfortabel Continuous Integration umgesetzt werden kann. Es steht unter Open-Source-Lizenz und kann auf cruisecontrol.sourceforge.net heruntergeladen werden. Der Download umfasst auch die Dokumentation des CruiseControl-Servers. Die Installation ist ähnlich wie bei fast allen Java-Tools und beschränkt sich auf das Entpacken. Ein Installer für Windows-Systeme ist ebenfalls verfügbar.
92
CruiseControl
Im Wurzelverzeichnis liegt unter Windows die Datei cruisecontrol.bat, welche den Server startet. Unter Linux/Unix ist es ein Shellskript (cruisecontrol.sh). Ist der CruiseControl-Server gestartet, kann man die Web-Applikation unter http://localhost:8080 erreichen. Neben einem Scheduler, der in definierten Intervallen Builds durchführen kann, gehören zu CruiseControl auch Reports (z.B. via E-Mail), Konfigurationsanwendungen und ein lokaler Webserver, der Informationen zu den abgelaufenen Builds präsentiert. Die Konfiguration von CruiseControl ist ausgelegt auf JUnit und muss für TestNG angepasst werden. Das folgende Listing zeigt die Konfiguration eines TestNG-Projekts in der config.xml.
Abb. 7.3: CruiseControl-Web-Report
Dabei wird hier für die allgemeine Konfiguration eines Projekts, dessen CVS/SVN-Zugang und die E-Mail-Einstellungen auf die CruiseControl-Dokumentation verwiesen. Ein guter Einstieg ist auch das Beispielprojekt, welches in der config.xml-Datei bereits konfiguriert ist.
schnell + kompakt
93
7 – Automatisierung und Buildtools
<project name="TNG" buildafterfailed="false"> <listeners> <currentbuildstatuslistener file="logs/ ${prj.name}/status.txt"/> ... <modificationset>... <schedule interval="300"> <merge dir="projects/${prj}/ngreport"/> ...
Voraussetzung für die Verwendung von CruiseControl in dieser Konfiguration ist ein Projekt, das mittels Ant gebaut und getestet werden kann. Ausgeführt wird das Target test. Dieses sollte in Abhängigkeit zu clean, init und compile stehen, um einen kompletten Build-Vorgang zu initiieren. Einen stets korrekten Ablauf erzeugt man, wenn vor einem Build-Lauf ein clean ausgeführt wird. Damit kann man Abhängigkeiten zu alten Builds auflösen und Fehler schneller aufspüren. Ebenso verhindert dies die Unvollständigkeit des Buildfile. Die Konfiguration eines TestNG-Target wurde bereits ausführlich in diesem Kapitel besprochen. Wichtig für die Integration in CruiseControl ist lediglich, dass der Report unter dem Pfad des merge dir abgelegt wird (projects/${prj}/ngreport).
94
CruiseControl
Neben den gezeigten Einstellungen muss auch sichergestellt werden, dass die TestNG-DTD im Klassenpfad von CruiseControl gefunden wird. Am einfachsten erreicht man dies, wenn diese Datei im Wurzelverzeichnis des Servers liegt und in der testng.xml die Datei ohne weiteren Pfad angegeben wird. Unter Unix-Systemen kann ein Symbolic-Link verwendet werden. Ebenso ist die Angabe zur URL (http://testng.org/testng-1.0.dtd) möglich, das verhindert aber die Verwendung des Buildfile im Offline-Modus. Hinweis Die Fehlermeldung, die der CC-Server ausgibt, falls die DTD nicht gefunden wird, ist: „No Testsuite Found“. Die richtige Fehlermeldung erhält man erst nach dem Studium der LogDatei.
schnell + kompakt
95
KAPITEL 8 Verwendung von IDEs 8.1
Eclipse
8.2
IDEA
100
97
8.3
NetBeans
102
Die Integration von Unittest-Tools in integrierte Entwicklungsumgebungen ist sehr beliebt. Unittests werden von Entwicklern durchgeführt, um den eigenen Code zu testen. Um dies möglichst komfortabel zu gestalten, sollten sie direkt während der Entwicklung ausgeführt werden können. Das aus JUnit bekannte Prinzip „Keep the bar green to keep the code clean…“ gilt ebenso für TestNG. Im Folgenden wird beschrieben, wie sich die Integration in die wichtigsten Java-Entwicklungsumgebungen gestaltet, wie die Installation durchgeführt wird und welche Unterschiede zwischen den Plug-ins bestehen.
8.1 Eclipse Die Integration in die weit verbreitete IDE Eclipse geschieht, wie üblich, über den Update-Mechanismus in Eclipse. Man trägt die Adresse der Update-Seite (http://beust.com/eclipse) in den Update/Install-Dialog innerhalb von Eclipse ein und installiert das aktuelle TestNG-Plug-in.
schnell + kompakt
97
8 – Verwendung von IDEs
Ist die Installation abgeschlossen, befindet sich in den EclipseEinstellungen (Window/Preferences) ein Eintrag TestNG, der es erlaubt, das Verzeichnis festzulegen, in dem die HTML-Reports der Testläufe liegen sollen. Diese Einstellung kann auch projektspezifisch getroffen werden. Bei den Projekteinstellungen ist außerdem ein Eintrag verfügbar, der es erlaubt, weitere Listener und Reporter anzugeben (vgl. Seite 81). Eclipse stellt außerdem eine TestNG-View zur Verfügung, die sehr stark an die JUnit-View erinnert. Zusätzlich sind im Kontextmenü der Klassen und Projekte TestNG-Einträge verfügbar, die es erlauben, die entsprechenden Klassen als Test auszuführen. Hat man bereits JUnit-Testklassen innerhalb eines Projekts, so ist über einen „Quickfix“ das Konvertieren zu TestNG-Tests möglich. Die TestNG View ist mit der Debug-Perspektive verknüpft, kann aber auch als View in jeder anderen Perspektive verwendet werden. In der Run/Debug-Konfiguration kann man nun, abhängig vom auszuführenden Test, einstellen, welche Klassen oder Testgruppen ausgeführt werden sollen (vgl. Abbildung 8.1). Ist schon eine testng.xml-Datei erstellt, z.B. um sie in Ant, Maven oder der Kommandozeile zu nutzen, kann man alternativ auch diese benutzen. Für jede dieser Möglichkeiten stehen Auswahldialoge zur Verfügung, die mögliche Einstellungen vorschlagen. Dazu durchsuchen Sie das aktuelle Projekt und stellen Testläufe zur Auswahl. Hat man jedoch eine komplexere Umgebung, z.B. verknüpfte Projekte und Abhängigkeiten zu Bibliotheken aus anderen Projektteilen, so ist es notwendig, TestNG innerhalb von Eclipse weiter zu konfigurieren.
98
Eclipse
Abb. 8.1: Run/Debug-Konfiguration von Eclipse
Alle Bibliotheken, die für das aktuelle Eclipse-Projekt eingestellt sind, werden auch von TestNG benutzt. In der Run/Debug-Konfiguration lassen sich weitere Bibliotheken hinzufügen und, was sicher noch wichtiger ist, der Ausführungsort des Tests angeben. So ist es auch möglich, Tests im Build-Ordner des Projekts auszuführen, mit den Klassen, die auch tatsächlich durch Buildskripte und Deploymenttools zusammengestellt worden sind. Das Eclipse-Plug-in erzeugt aus den Einstellungen zum Test eine .launch-Datei. In ihr werden alle Einstellungen des konfigurier-
ten Testlaufs eingetragen. Sie beinhaltet auch die Classpath-
schnell + kompakt
99
8 – Verwendung von IDEs
Angaben sowie Einstellungen zu den durchzuführenden Tests. Wenn mehrere Entwickler innerhalb eines Projekts das Plug-in nutzen, sollte diese Datei zusammen mit den Sourcen auf dem Code-Versionierungsserver liegen.
8.2 IDEA Die Integration in JetBrains IntelliJIDEA ab Version 5.0 ist ebenso gut und einfach wie bei Eclipse. Die Installation geht sogar noch einfacher, indem man das TestNG-Plug-in aus der Liste der zur Verfügung stehenden Plug-ins auswählt. Im Standard-Repository für IDEA ist bereits die aktuellste Version des TestNG-Plug-in verzeichnet. Ein manuelles Hinzufügen entfällt. Zusätzlich sind weitere Plug-ins für TestNG verfügbar, wie der TestNG Generator, der Templates für TestNG-Tests bereitstellt. Diese enthalten dann bereits die wichtigsten Methodenrümpfe. Zusätzlich wurde das Testdox-Plug-in um die Unterstützung für TestNG und JUnit4 erweitert. Es ist dadurch in der Lage, auf Annotations zu reagieren. In Abbildung 8.2 ist der entsprechende Auswahldialog zu sehen, mit dem die TestNG-Plug-ins installiert werden, die nach einem Neustart dann zur Verfügung stehen. Über das TestNG Generator-Plug-in lässt sich für jede zu testende Klasse eine TestNG-Testklasse erstellen. Durch den Shortcut Ç+steht der neue Menüpunkt GENERATE TESTNG TEST CLASS zur Verfügung. Dieser erstellt eine neue Klasse, deren Package-Name mit test beginnt, der Klassenname selbst bekommt das Suffix Test. Das eigentliche TestNG-Plug-in fügt einen Menüpunkt in die Run/Debug-Konfiguration ein. Hier lässt sich einstellen, welche Tests ausgeführt werden sollen (package, group, class, method).
100
IDEA
Abb. 8.2: IntelliJIDEA-Installationsdialog
Zusätzlich − ganz im Sinne der IDEA-Semantik − kann man einstellen, ob die Tests im gesamten Projekt oder nur in Modulen ausgeführt werden sollen. Parameter für die verwendete JVM lassen sich genauso bereitstellen, wie auch Parameter, die im Test verwendet werden können. Die Anzeige für absolvierte Tests ist etwas schlichter geraten als bei Eclipse. Neben der Baumansicht, in der die verschiedenen durchlaufenen Testmethoden mit Status (Pass/Fail) angezeigt werden, ist der obligatorische grüne bzw. rote Balken dominant und zeigt, ob der gesamte Testverlauf erfolgreich war. Das Testdox-Plug-in erleichtert die Navigation zwischen Test und zu testender Klasse, das Umbenennen und Erstellen von Tests und deren Dokumentation. Hinweis Im Gegensatz zu den Plug-ins der anderen IDEs gibt es keinen HTML-Output, dieser kann aber über die Ausführung von Ant, Maven oder der Kommandozeile generiert werden.
schnell + kompakt
101
8 – Verwendung von IDEs
8.3 NetBeans Auch eine Integration in NetBeans ab Version 5.0 ist seit Mitte 2006 verfügbar. Bisher wurde das Plug-in noch nicht auf der offiziellen TestNG-Webseite verlinkt. Bereitgestellt wird das Plug-in durch die Tools des Projekts nbxdoclet (nbxdoclet.sourceforge.net). Neben TestNG beinhaltet dieses Projekt zahlreiche weitere Erweiterungen für Hibernate, XDoclet und andere Projekte. Auf der Sourceforge-Seite steht eine .nbm-Datei zur Verfügung, die in das Update Center (Tools/Update Center) von NetBeans eingetragen werden kann. Anschließend verbindet man sich über das Update Center mit dem „NBXDoclet Update Center“ und fügt das TestNG-Plug-in hinzu. Ist das Plug-in dann installiert und NetBeans neu gestartet, steht ein Template für TestNG-Testklassen zur Verfügung, in dem bereits fertige Methodenrümpfe für die Initialisierung und Beendigung der Tests vorhanden sind. Weiterhin wird die neue Testklasse einer Testgruppe hinzugefügt.
Abb. 8.3: New File-Dialog für TestNG-Klassen in NetBeans
Was jedoch die Entwicklung von TestNG-Testklassen mit diesem Plug-in sehr komfortabel gestaltet, ist der Properties Editor. In der XDoclet View werden die Eigenschaften angezeigt, die für die
102
NetBeans
entsprechenden TestNG-Annotations zur Verfügung stehen. Einfache Attribute, die nur true oder false annehmen können, besitzen eine Checkbox. Andere Attribute, wie z.B. Groups, haben einen Auswahldialog, der es ermöglicht, Klassen und Methoden zu bereits vorhandenen Testgruppen hinzuzufügen.
Abb. 8.4: NetBeans Properties Editor
In der Project Extension View befindet sich ein kleines Auswahlmenü, über das die Testläufe ausgeführt werden können. Anders als bei den zuvor dargestellten IDE-Integrationen gibt es keinen „Grünen Balken“ und ebenso keine Darstellung der einzelnen Tests bzw. deren Stack Traces. Um die Ergebnisse zu präsentieren, werden die von TestNG generierten HTML-Reports im Browser aufgerufen. Diese liegen im Projektordner unter build\ test\ngreport.
schnell + kompakt
103
KAPITEL 9 JavaEE testen
Dieses Buch kann nicht die gesamte Problematik des Testens von Java-Enterprise-Applikationen darstellen und soll an dieser Stelle nur einen Einblick geben, wie TestNG in diesem Umfeld verwendet werden kann. Der Test von Java-Enterprise-Anwendungen gestaltet sich in der Regel sehr schwierig. Zuerst muss man sich entscheiden, welche Art von Tests ausgeführt werden sollen. Will man sich auf Unittests der Methoden seiner Business-Klassen beschränken oder soll auch das Gesamtsystem untersucht werden (Integrationstests). Im JUnit-Umfeld gibt es zahlreiche Erweiterungen, die z.B. den HTML-Code eines Web-Frontend mit dem Erwarteten vergleichen können. Ebenso gibt es Frameworks für In-Container-Tests, die im laufenden Applikations-Server die Tests ausführen. Generell gibt es ebenso die Möglichkeit, die Klassen in Isolation zu testen. Dazu benutzt man Mock-Objekte, welche die Schnittstellen der Applikationsserver emulieren. Der Aufwand für die Erstellung der Mock-Objekte unterscheidet sich, je nach Framework und Test. Bei der Integration einer solchen „gemockten“
schnell + kompakt
105
9 – JavaEE testen
Anwendung können jedoch Probleme auftauchen. Dies ist besonders dann der Fall, wenn sich der Code einer Klasse ändert, aber das Mock nicht ebenfalls geändert wird. Die letzte Möglichkeit, die hier angesprochen werden soll, ist das Testen der Applikation über die Remote-Interfaces. Dadurch sind die Objekte in ihrer „natürlichen Umgebung“ testbar, jedoch müssen alle zu testenden Klassen auch diese Interfaces bereitstellen. Seit EJB 3 entfällt diese Funktionalität weitgehend und gilt also nur für J2EE-Anwendungen. Ein wenig Erleichterung in der ganzen Testproblematik unter J2EE bringt die Einführung von POJO-basierten Frameworks wie z.B. Spring oder auch die Einführung von EJB 3.0, bei denen ja ebenfalls der POJO-Ansatz verfolgt wird. Einfacheres Testen ist einer der Beweggründe, die zu solchen leichtgewichtigeren Ansätzen geführt haben. Im Folgenden wird darauf eingegangen, wie EJB3-Anwendungen testbar werden. J2EE-Anwendungen sollen hier jedoch nicht weiter betrachtet werden, da die Optionen, die für JUnit bereitstehen, in TestNG nachempfunden oder integriert werden können. Die sehr einfache Ausführung von TestNG, aus Java-Code heraus, bietet auch die Möglichkeit, TestNG-Tests auf einem Container zu deployen und die Tests an Ort und Stelle auszuführen. Dass TestNG auch in „Real World Applications“ eingesetzt wird, die im Java-Enterprise-Umfeld angesiedelt sind, zeigen beispielhaft zwei Projekte. Zum einen ist dies JBoss-Seam – ein Web-Application-Framework, basierend auf JSF und EJB 3.0. Das zweite Projekt, das hier angesprochen werden soll, ist der „Glassfish Application Server“. Er stellt die Code-Basis für die Referenzimplementierung „Sun Java System Application Server“ zur Verfügung. Beide Projekte verwenden TestNG für alle Tests, geben Feedback an die TestNG-Entwickler und helfen bei der Weiterentwicklung von TestNG.
106
JavaEE testen
Das JBoss-Seam-Projekt ist ein sehr guter Einstieg in das Testen von EJB-3.0-Anwendungen. Die Beispielapplikation liefert mit dem Quellcode der Anwendung auch alle TestNG-Unittests, die auf eigene Applikationen übertragen werden können. Getestet wird mithilfe des JBoss Embeddable EJB3 Containers und einer Klasse im Kern von Seam (SeamTest), welche die Callback-Funktionen des Containers an den Test überträgt. Das JBoss-Wiki bietet auch ein vollständiges Eclipse-Projekt unter wiki.jboss.org/wiki/Wiki.jsp?page=StarterSkeletonEclipseProject. Die beigefügte zip-Datei enthält sowohl den EJB3- als auch den Testcode, eine Build-Datei und die TestNG-Konfiguration. Die Anleitung beschreibt, wie man eine EJB3-Anwendung erstellt, die auf JBoss-Seam basiert. Ist dies geglückt, zeigt die Anleitung, wie man die Anwendung per TestNG mithilfe des „EmbeddableEJB3-Containers“ startet, deployed und testet. Dass dieses Beispiel auf Seam basiert, ist jedoch keine Einschränkung. Durch Anpassung der Konfiguration sind auf diese Weise auch andere JavaEE-Projekte testbar. Einzige Voraussetzung für das Beispiel ist der JBoss Embeddable EJB3 Container. Der Test startet diesen und deployed die Anwendung. Solange die Anwendung standardkonform ist, kann dieser Container auch für das Testen von Applikationen eingesetzt werden, die nicht auf dem JBoss laufen sollen. Einen anderen Ausgangspunkt für die eigene Test-Suite stellt das Projekt CaveatEmptor (caveatemptor.hibernate.org) bereit. Auch dieses benutzt TestNG, um das Beispielprojekt für Hibernate zu testen. Es liegt ebenfalls im Quellcode vor und bietet daher sehr gute Möglichkeiten für die Evaluation. Das zweite große Projekt, der Glassfish Application Server, bietet neben dem Quellcode auch die Dokumente, die als Entscheidungsgrundlage für den Einsatz von TestNG dienten. Sie können helfen, die Entscheidung zu Gunsten von TestNG auch im eige-
schnell + kompakt
107
9 – JavaEE testen
nen Unternehmen zu unterstützen, und sind unter folgender Adresse abrufbar: wiki.java.net/bin/view/Projects/GlassFishQuality Ganz nebenbei bietet das große Code-Repository sehr viele Testbeispiele. Asynchrones Testen Ein weiteres Thema, das im Bereich JavaEE auftaucht, ist der Einsatz von JMS, also asynchronen Nachrichten. TestNG unterstützt das Testen dieser Anwendungen durch das timeout-Attribut. Dieses Attribut ist bereits im Kapitel zu Ant angesprochen worden, definiert dort jedoch den Timeout für den gesamten Testlauf. Innerhalb einer Testklasse kann dieses Attribut wesentlich gezielter eingesetzt werden, die Testklasse muss natürlich das Interface javax.jms.MessageListener implementieren: @Test(timeout = 5000 /* millisekunden */) public waitForMessage() { m_received.wait(); } public void onMessage(Message msg) { if (msg.equals(sampleObj)) { m_received.notify(); } }
Das Beispiel zeigt, wie man definiert, dass eine JMS-Nachricht innerhalb von fünf Sekunden eintreffen muss. Dabei muss die Nachricht bestimmte Eigenschaften aufweisen, die im if-Block abgefragt werden.
108
KAPITEL 10 JTiger
Aus den Unzulänglichkeiten, die zu TestNG führten, entwickelte sich ebenso das Framework JTiger, das hier nicht unerwähnt bleiben soll. Tony Morris entwickelte JTiger für ein Projekt einer Firma, in der er unter anderem für Software-Tests zuständig war. Die bestehenden Lösungen, und vor allem JUnit, hatten nicht die gewünschte Funktionalität. Das eigene Framework sollte außerdem einen höheren Komfort bieten. Um die Handhabung zu vereinfachen, wurden eine Reihe von erweiterten Assertions in JTiger aufgenommen, die mehr als einen True/False-Vergleich bereitstellen. Dadurch war es auch möglich, zu verhindern, dass Standardvergleiche innerhalb der Tests geschrieben werden müssen. Solcher Code ist durch die häufige Verwendung ein ideales Beispiel für das Auslagern in eine separate Funktion, hier in das JTiger-Framework. Auch Code-Coverage (vgl. Kapitel 6) wurde, durch die Integration von Emma, direkt im Framework bereitgestellt. Auf Grundlage dieser Gedanken entwickelte Toni Morris das Test-Framework JTiger und stellte es auf der Webseite (jtiger.org) frei zur Verfügung (CPL v1.0).
schnell + kompakt
109
10 – JTiger
Um den gewünschten Komfort zu erzielen, setzt JTiger genau wie TestNG auf die Features von Java 5.0, Annotations, Generics, variable Parameterlisten und Static Imports. Um eine leichte Migration von JUnit zu ermöglichen, hat JTiger ein JUnit-Plug-in, um JUnit-Testfälle auszuführen. Die Organisation der Tests wird in JTiger in logisch zusammengehörige Gruppen gegliedert. In JTiger heißt das Fixture und bezeichnet eine Java-Klasse, in der sich eine oder mehrere zusammengehörige Testmethoden befinden. Die Fixtures lassen sich dann weiter zu Kategorien zusammenfassen, analog zu den Groups in TestNG. JTiger bietet die Möglichkeit, die Ergebnisse der Testläufe in verschiedenen Formaten zu präsentieren. Zur Verfügung stehen XML, HTML und einfacher Text. Durch Erweiterung des Interface FixtureResultsHandler ist es möglich, den Output von JTiger auch auf eigene Bedürfnisse anzupassen, z.B. um den Output in einem anderen Programm zu nutzen. Die Besonderheit, die JTiger jedoch auszeichnet, ist die Anzahl der bereits mitgelieferten Assertions, die nicht nur bei Parameterübergabe auf gleich und ungleich überprüfen, sondern z.B. auf die Erfüllung des ObjectFactory Contract (Objekte sind gleich, aber nicht identisch usw.). Weitere Assertions überprüfen, ob HashCode und Equals überschrieben und Comparable implementiert wurde oder ob ein Objekt serializable ist. Das folgende Beispiel soll zeigen, wie diese Assertions benutzt werden können. @Test public void factoryProof() { assertObjectFactoryFillsContract(msgfactory); }
110
JTiger
@Test public void messageTransferable() { assertSerializes(MsgFactory.getInstance()); }
Dieses Feature ist wohl das einzige, das TestNG und JUnit nicht in gleichem Umfang bereitstellen, es ist aber leicht zu integrieren. Leider scheint das Projekt langsam einzuschlafen. Seit Ende 2005 gab es keine Neuerungen mehr und die Community ist auch bei weitem kleiner oder nicht so publikationsfreudig, wie die der anderen Frameworks.
schnell + kompakt
111
KAPITEL 11 Vergleich der Test-Frameworks 11.1 Fazit
116
Nachdem JUnit 4 Anfang 2006 erschienen ist, waren die Gründe, zu TestNG oder JTiger zu wechseln, nicht mehr ganz so offensichtlich. Schließlich basieren nun alle nennenswerten Frameworks auf Annotations, sie bieten die Möglichkeit, Testmethoden ohne Präfix oder Suffix zu benennen, und es ist nicht mehr nötig, die Testklasse von einer Framework-Klasse abzuleiten. JUnit zog nach und integrierte viele Features, die TestNG vorgestellt hat, ebenfalls. Das Konzept von JUnit, nämlich Testen in Isolation, ist jedoch dem weit offeneren Konzept in TestNG unterlegen. TestNG war von Anfang an darauf ausgerichtet, auch Integrationstests durchzuführen, was sich in JUnit nur schwer realisieren lässt. Die Möglichkeiten, die JUnit bietet, sind darauf beschränkt, vor und nach einem Test oder einer Methode bestimmte Initialisierungen durchzuführen. Das Ausführen einer ganzen Abfolge von Tests, die auch komplexe Integrationstests erlauben würden, ist nicht vorgesehen. Es gibt dafür „Workarounds“, aber dies ist bei weitem nicht so komfortabel, wie Abhängigkeiten zwischen den Tests zu definieren. Diese Eigenschaft in TestNG erlaubt es auch, festzustellen, ob Tests wirklich fehlschlugen, oder nur ein bestimmter Test, von dem andere ab-
schnell + kompakt
113
11 – Vergleich der Test-Frameworks
hängig sind. In TestNG werden solche Tests als „skipped“ markiert. Da in JUnit keine Reihenfolge der Tests auszumachen ist, ist die Fehlersuche schwer, wenn zahlreiche Tests plötzlich fehlschlagen. Um den Vergleich übersichtlicher darzustellen, soll die folgende Tabelle zeigen, welche Erwartungen man an die Test-Frameworks haben kann: JUnit 4.x
TestNG 5.x
JTiger 2.1
Annotations
+
+
+
Test v. < 5.0 Code
+
+
+
Dependend Tests
-
+
-
Skip-Tests
-
+
-
Wiederholung gescheiterter Tests
-
+
-
Testgruppen
-
+
ähnlich
Parametertests und Testdaten
z.B. FITFramework
+ (nativ)
-
Plug-ins und Erweiterungen
fast alle Testerweiterungen sind für JUnit verfügbar
IDEs, Buildtools, viele JUnit-Tools verwendbar
Eclipse, IDEA, Ant
Reports
HTML, PDF, XML
HTML, XML
HTML, XML, Text
Parallele Tests
-
+
-
114
Vergleich der Test-Frameworks
Verteilte Tests
JUnit 4.x
TestNG 5.x
JTiger 2.1
-
+
-
Dies ist nur ein Ausschnitt der Features dieser Frameworks. Besonders die vielen Erweiterungen, die auf JUnit aufbauen, erlauben es, auch mit JUnit komfortabel zu testen. Und den Einsatzbereich weit über das hinauszubringen, was JUnit im Kern zu leisten im Stande ist. Doch der Einsatz zahlreicher Erweiterungen benötigt auch die Einarbeitung in die verschiedenen Tools und deren Konfiguration. TestNG besticht durch die zahlreichen Features, die bereits dessen Kern liefert. Es ist konsequent darauf ausgelegt, auch Integrationstests durchzuführen, und erweitert sich in Zusammenarbeit mit der Community schnell weiter. Die Bearbeitung von Feature-Requests kann in der Mailinglist/im Forum nachvollzogen werden. Die Initiatoren scheinen jeden Beitrag zu lesen. Gefundene Fehler, die meist in Zusammenhang mit Erweiterungen auftreten, werden umgehend behoben. Im aktuellen Release von TestNG wurde noch einmal die Integration von JUnit verbessert. Die JUnit-Funktionalität wird nun nicht mehr durch TestNG-Funktionen gekapselt. Der JUnit-3Kern wurde direkt integriert, so dass es die Tests ausführt und das Ergebnis für TestNG bereitstellt. Damit dürfte sich dann wirklich jede vorhandene Test-Suite sanft migrieren lassen und die Erweiterungen, die eigentlich für JUnit entwickelt sind, lassen sich dadurch ebenfalls mit TestNG nutzen. Zu JTiger lässt sich sagen: Nachdem Tony Morris das Projekt abgegeben hatte, ist nicht mehr viel geschehen. Seit Ende 2005 gab es keine Neuerungen. Da Zukunftssicherheit und Pflege gerade von Open-Source-Tools wichtige Entscheidungskriterien sind,
schnell + kompakt
115
11 – Vergleich der Test-Frameworks
dürfte das nicht besonders für das Framework sprechen, auch wenn es bis zum Release 2.1 ebenbürtig zu TestNG war.
11.1 Fazit Jeder Entwickler sollte sich über die Reichweite seines Projekts bewusst sein. Geht es lediglich um kleine lokale Tests, die auch in der IDE sinnvoll ausgeführt werden sollen, so ist JUnit 4 eine gute Wahl und in vielen Fällen auch ausreichend. Ist das Projekt aber breiter angelegt, fallen dem Entwicklerteam viele Testgruppen ein oder müssen viele komplexe Integrationstests durchgeführt werden, so wird der Entwickler mit TestNG fast nie eine Grenze in Bezug auf seine Testanforderungen erfahren. Bei der Frage, welches Test-Framework man daher lernen sollte empfiehlt sich TestNG auf jeden Fall, da es quasi ein Superset von JUnit darstellt. TestNG kann ja auch JUnit-Tests ausführen, man bleibt also immer „abwärtskompatibel“. ;-)
116
Stichwortverzeichnis @Test 12, 37 3rdParty 17
A Abdeckung 59 abhängige Tests 40 AfterClass 46 AfterGroup 49 AfterMethod 47 AfterSuite 47 AfterTest 47 alwaysRun 42, 50 Annotations 11 Ant-Task 76 apt 11 Argumente 52 Assert 19 Automatisierte Tests 74 Automatisierung 73
B BeforeClass 46 BeforeGroup 49 BeforeMethod 47 BeforeSuite 47 BeforeTest 47 Bibliotheken 17, 20 Buildfile 20 Buildtools 73 Ant 76
C classes 32 classfileset 79 Classpath 20 Codebasis 73
schnell + kompakt
Code-Coverage 59 Continuous Integration 74 Coverage .ser-Datei 64 Branch-Coverage 60 Clover 68 Clover-report 70 Cobertura 63 Cobertura-Report 64 Coverage-Metriken 69 Emma 66 Emma-Report 67 Emma-Task 66 Historical Reports 69 Line-Coverage 60 metadatafile 66 Method-Coverage 60 PDF-Report 69 with.clover 69 CruiseControl 74, 92 config.xml 93 Continuous Integration 92 cruisecontrol.bat 93 cruisecontrol.sh 93 merge dir 94 Web-Report 93 CVS 73
D DataProvider 52, 54 dataProvider 42 dependsOnGroups 38, 50 dependsOnMethods 38, 50 description 43 Design by Contract 75
117
Stichwortverzeichnis
doc 17 DTD 29 dumpCommand 80
E Eclipse 97 Quickfix 98 Einführung 15 enabled 43, 51 examples 17 Exception 19, 23 expectedException 40
F Factories 56 failureProperty 81
G Group 34 Groups 34 groups 38, 51
H holdonfailure 81 holdonskipped 81
I IDEA 100 Installation 16 instrumentiert 60 Integrationstests 75 Interfaces 12 invocationCount 44 Iteratoren 53
JBoss Seam 107 JMS 108 JTiger 24, 109 Assertions 110 JUnit 24
K Konfigurationen 25 Konsole 25, 27
M Marker 13 Martin Fowler 74 Maven 82 Dependency Management 86 Lifecycle 86 Maven 1 83 Maven 2 86 Maven-Proxy 86 POM 86 pom.xml 87 Remote-Repository 83 Site-Plug-in 92 surefire 87 TestNG-Goal 83 Meta-Groups 35 Methods 33 Mock-Objekt 52 Mock-Objekte 75
N name 29 NetBeans 102 Properties Editor 102
J
O
Jars 18 Javadoc 17 JavaEE 105 Asynchrones Testen 108 Glassfish AS 107
outputDir 77
118
P Packages 32 parallel 30
Stichwortverzeichnis
Parameter 36 Parameterübergabe 51 Parametrisierte DataProvider 54
R Report 22 run 34 runs 34
S sequential 44 setUp 31, 45 skippedProperty 81 sourcedir 77 Sourcen 18 Subversion 73 successPercentage 44 suite 29
T tearDown 31, 45 Test 31 test 31 Test Driven Developement 59 Test Factories 56
schnell + kompakt
testen 18 Testgruppen 16 testjar 81 testng.xml 77 testng-failed.xml 78 Testrunner 18 thread-count 30 threadPoolSize 45 Threads 45 timeout 45, 81
V value 13 verbose 29, 77 Vergleich 114
W whiteBox-Tests 40 Wildcards 21 workingdir 81
X XDoclet 11 xmlfileset 77
119