{"id":560,"date":"2010-04-15T11:31:46","date_gmt":"2010-04-15T09:31:46","guid":{"rendered":"http:\/\/www.tutego.de\/blog\/javainsel\/2010\/04\/junit-4-tutorial-java-tests-mit-junit\/"},"modified":"2015-01-19T13:49:26","modified_gmt":"2015-01-19T11:49:26","slug":"junit-4-tutorial-java-tests-mit-junit","status":"publish","type":"post","link":"https:\/\/www.tutego.de\/blog\/javainsel\/2010\/04\/junit-4-tutorial-java-tests-mit-junit\/","title":{"rendered":"JUnit 4 Tutorial, Java-Tests mit JUnit"},"content":{"rendered":"<p><strong>Hinweis! Eine aktuelle Version des Abschnittes gibt es im Buch &#8222;Java ist auch eine Insel. Einf\u00fchrung, Ausbildung, Praxis&#8220;, dem 1. Inselbuch. (War f\u00fcr den 2. Band vorgesehen, musste wegen der F\u00fclle des 2. Buchs kurzerhand in den 1. Band geschoben werden. Sorry f\u00fcr Irritationen!)<\/strong><\/p>\n<h3>1.1 Softwaretests<\/h3>\n<p>Um m\u00f6glichst viel Vertrauen in die eigene Codebasis zu bekommen, bieten sich Softwaretests an. Tests sind kleine Programme, die ohne Benutzerkontrolle automatisch \u00fcber die Quellcodebasis laufen und anhand von Regeln zeigen, dass gew\u00fcnschte Teile sich so verhalten wie gew\u00fcnscht. (Die Abwesenheit von Fehlern kann eine Software nat\u00fcrlich nicht zeigen, aber immer zeigt ein Testfall, dass das Programm die Vorgaben aus der Spezifikation erf\u00fcllt.)<\/p>\n<p>Obwohl Softwaretests extrem wichtig sind, sind sie unter Softwareentwicklern nicht unbedingt popul\u00e4r. Das liegt unter anderem daran, dass sie nat\u00fcrlich etwas Zeit kosten, die neben der tats\u00e4chlichen Entwicklung aufgewendet werden muss. Wenn dann die eigentliche Software ge\u00e4ndert wird, m\u00fcssen auch die Testf\u00e4lle oftmals mit angefasst werden, so dass es gleich zwei Baustellen gibt. Und da Entwickler eigentlich immer gestern das Feature fertig stellen sollten, fallen die Testes gerne unter den Tisch. Ein weiterer Grund ist, dass einige Entwickler sich f\u00fcr unfehlbare Codierungsg\u00f6tter halten, die jeden Programmcode (nach ein paar Stunden debuggen) f\u00fcr absolut korrekt, performant und wohl duftend halten.<\/p>\n<p>Wie l\u00e4sst sich diese skeptische Gruppe nun \u00fcberzeugen, doch Tests zu schreiben? Ein gro\u00dfer Vorteil von automatisierten Tests ist die Eigenschaft, dass bei gro\u00dfen \u00c4nderungen der Quellcodebasis (Refactoring) die Testf\u00e4lle automatisch sagen, ob alles korrekt ver\u00e4ndert wurde. Denn wenn nach dem Refactoring, etwa einer Performance-Optimierung, die Tests einen Fehler melden, ist wohl etwas kaputt-optimiert worden. Da Software einer permanenten \u00c4nderung unterliegt und nie fertig ist, sollte das Argument eigentlich schon ausreichen, denn wenn eine Software eine gewisse Gr\u00f6\u00dfe erreicht hat, ist nicht absehbar, welche Auswirklungen \u00c4nderungen an der Quellcodebasis nach sich ziehen. Dazu kommt ein weiterer Grund, sich mit Tests zu besch\u00e4ftigen. Es ist der positive Nebeneffekt, dass die erzeugte Software vom Design deutlich besser ist, denn testbare Software zu schreiben ist knifflig, f\u00fchrt aber fast zwangsl\u00e4ufig zum besseren Design. Und ein besseres Design ist immer erstrebenswert, denn das erh\u00f6ht die Verst\u00e4ndlichkeit und erleichtert die sp\u00e4tere Anpassung der Software.<\/p>\n<h5>1.1.1 Vorgehen bei Schreiben von Testf\u00e4llen<\/h5>\n<p>Die Fokussierung bei Softwaretests liegt auf zwei Attributen: automatisch und wiederholbar. Dazu ist eine Bibliothek n\u00f6tig, die zwei Dinge unterst\u00fctzen muss.<\/p>\n<p>\u00b7 Testf\u00e4lle sehen immer gleich aus und bestehen aus drei Schritten. Zun\u00e4chst wird ein Szenario aufgebaut, dann die zu testende Methode oder Methodenkombination aufgerufen und zum Schluss mit der spezifischen API vom Testframework geschaut, ob das ausgef\u00fchrte Programm genau das gew\u00fcnschte Verhalten gebracht hat. Das \u00dcbernehmen eine Art \u201estimmt-es-dass\u201c-Methoden, die den gew\u00fcnschten Zustand mit dem tats\u00e4chlichen Abgleichen und beim Konflikt eine Ausnahme ausl\u00f6sen.<\/p>\n<p>\u00b7 Das Testframework muss die Tests laufen lassen und im Fehlerfall eine Meldung geben; der Teil nennt sich <em>Test-Runner<\/em>.<\/p>\n<p>Wir werden uns im Folgenden auf sogenannte <em>Unit-Tests<\/em> beschr\u00e4nken. Das sind Tests, die einzelne Bausteine (engl. units) testen. Daneben gibt es andere Tests, wie Lasttests, Performance-Tests oder Integrationstests, die aber im Folgenden keine gro\u00dfe Rolle spielen.<\/p>\n<p><!--more--><\/p>\n<h4>1.2 Das Testframework JUnit<\/h4>\n<p>Sun definiert kein allgemeines Standardframework zur Definition von Unit-Testf\u00e4llen, noch bietet es eine Ablaufumgebung f\u00fcr Testf\u00e4lle. Diese L\u00fccke f\u00fcllen Testframeworks, wobei die popul\u00e4rsten im Java-Bereich das freie quelloffene <em>JUnit<\/em> (<em>http:\/\/www.junit.org\/<\/em>) und (mit Abstand) <em>TestNG<\/em> (<em>http:\/\/testng.org\/<\/em>) sind. Im Folgenden wollen wir uns auf JUnit konzentrieren, ein Framework, welches von Kent Beck und Erich Gamma entwickelt wurde. Um die aktuellste Version von JUnit im Klassenpfad zu haben (IDEs tendieren zu \u00e4lteren Versionen, auch wenn sich JUnit nicht in Lichtgeschwindigkeit entwickelt). Dazu ist von der Download-Seite der JUnit-Homepage, die auf <em>http:\/\/sourceforge.net\/projects\/junit\/files\/junit\/<\/em> weiterleitet, das entsprechende Jar-Archiv zu laden, etwa <em>junit-dep-4.7.jar<\/em> (<em>junit-4.7.jar<\/em> sollte es f\u00fcr das sp\u00e4tere Hamcrest nicht sein, funktioniert aber sonst), und im Klassenpfad aufzunehmen.<\/p>\n<p>Die Standard-IDEs Eclipse, NetBeans, IntelliJ bringen JUnit gleich mit und bieten Wizards zum einfachen Erstellen von Testf\u00e4llen aus vorhandenen Klassen an. \u00dcber Tastendruck lassen sich Testf\u00e4lle abarbeiten und ein farbiger Balken zeigt direkt an, ob wir unsere Arbeit gut gemacht haben.<\/p>\n<h5>1.2.1 Test-Driven-Development und Test-First<\/h5>\n<p>Unser JUnit-Beispiel wollen wir nach einem ganz speziellen Ansatz entwickeln, welches sich <em>Test-First<\/em> nennt. Dabei wird der Testfall noch vor der eigentlichen Implementierung geschrieben. Die Reihenfolge mit dem Test-First-Ansatz sieht (etwas erweitert) so aus:<\/p>\n<ol>\n<li>\u00dcberlege, welche Klasse und Methode geschrieben werden soll. Lege Quellcode f\u00fcr die Klasse und Variablen\/Methoden\/Konstruktoren an, sodass sich die Compilationsheit \u00fcbersetzen l\u00e4sst.<\/li>\n<li>Schreibe die API-Dokumentation f\u00fcr die Methoden\/Konstruktoren und \u00fcberlege, welche Parameter, R\u00fcckgaben, Ausnahmen n\u00f6tig sind.<\/li>\n<li>Teste die API an einem Beispiel, ob sich die Klasse mit Eigenschaften \u201enat\u00fcrlich\u201c anf\u00fchlt. Falls n\u00f6tig wechsele zu Punkt 1 und passe die Eigenschaften an.<\/li>\n<li>Implementiere eine Testklasse.<\/li>\n<li>Implementiere die Logik des eigentlichen Programms.<\/li>\n<li>Gibt es durch die Implementierung neue Dinge, die ein Testfall testen sollte? Wenn ja, erweitere den Testfall.<\/li>\n<li>F\u00fchre die Tests aus und wiederhole Schritt 5 bis alles fehlerfrei l\u00e4uft.<\/li>\n<\/ol>\n<p>Test-Driven-Development hat den gro\u00dfen Vorteil, \u00fcberschnelle Entwickler, die ohne gro\u00df zu denken zur Tastatur greifen, dann implementieren und nach 20 Minuten wieder alles \u00e4ndern, zum Nachdenken zwingt. Gro\u00dfe \u00c4nderungen kosten Zeit und somit Geld und Test-First verringert die Notwendigkeit sp\u00e4terer \u00c4nderungen. Denn wenn Entwickler Zeit in die API-Dokumentation investieren und Testf\u00e4lle schreiben, dann haben sie eine sehr gute Vorstellung \u00fcber die Arbeitsweise der Klasse und gro\u00dfe \u00c4nderungen sind seltener.<\/p>\n<p>Der Test-First-Ansatz ist eine Anwendung von <em>Test-Driven-Development (TDD)<\/em>. Hier geht es darum, die Testbarkeit gleich als Ziel bei der Softwareentwicklung zu definieren. Hieran krankten fr\u00fchere Entwicklungsmodelle, etwa das wohlbekannte Wasserfallmodell, welches Testen an das Ende nach Analyse, Design und Implementierung stellte. Die Konsequenz dieser Reihenfolge war oft ein gro\u00dfer Klumpen von Programmcode der unm\u00f6glich zu testen war. Mit TDD soll das nicht mehr passieren. Heutzutage sollten Entwickler sich bei jeder Architektur, jedem Design und der Klasse gleich zu Beginn \u00fcberlegen, wie das Ergebnis zu testen ist. Untersuchungen zeigen: Mit TDD ist das Design signifikant besser.<\/p>\n<p>Zur Frage wann Tests durchgef\u00fchrt werden sollen l\u00e4sst sich nur eins festhalten: So oft wie m\u00f6glich. Denn je eher ein Test durch eine falsche Programm\u00e4nderung fehlschl\u00e4gt, desto eher kann der Fehler behoben werden. Gute Zeitpunkte sind daher vor und hinter gr\u00f6\u00dferen Design\u00e4nderungen und auf jeden Fall vor dem Einpflegen in die Versionsverwaltung. Im modernen Entwicklungsprozess gibt es einen Rechner, auf dem eine Software zur kontinuierlichen Integration l\u00e4uft (engl. Continuous Integration). Diese Systeme integrieren einen Build-Server, der die Quellen automatisch aus einer Versionsverwaltung auscheckt, compiliert und dann Testf\u00e4lle und weitere Metriken laufen l\u00e4sst. Diese Software \u00fcbernimmt dann einen <em>Integrationstest<\/em>, da hier alle Module der Software zu einer Gesamtheit zusammengebaut werden und so Probleme aufgezeigt werden, die sich vielleicht bei isolierten Tests auf den Entwicklermaschinen nicht zeigen.<\/p>\n<h5>1.2.2 Testen, implementieren, testen, implementieren, testen, freuen<\/h5>\n<p>Bisher bietet Java keine einfache Funktion, um Strings umzudrehen. Unser erstes JUnit-Beispiel soll daher um eine Klasse StringUtils gestrickt werden, die eine statische Methode reverse() anbietet. Nach dem TDD-Ansatz implementieren wir eine Klasse, die korrekt \u00fcbersetzt werden kann, aber bisher ohne Funktionalit\u00e4t ist. (Auf die API-Dokumentation verzichtet das Beispiel.)<\/p>\n<p>com\/tutego\/insel\/junit\/util\/StringUtils.java, StringUtils<\/p>\n<pre>public class StringUtils\r\n{\r\n<strong> public static String reverse( String string )\r\n<\/strong><strong> {\r\n<\/strong><strong>  return null;\r\n<\/strong><strong> }\r\n<\/strong>}<\/pre>\n<p>Gegen diese eigene API l\u00e4sst sich nun der Testfall schreiben. Spontan f\u00e4llt uns ein, dass ein Leerstring umgedreht nat\u00fcrlich auch ein Leerstring ergibt und die Zeichenkette \u201eabc\u201c umgedreht \u201ecba\u201c ergibt. Ziel ist es, eine m\u00f6glichst gute <em>Abdeckung<\/em> aller F\u00e4lle zu bekommen. Wenn wir Fallunterscheidungen im Programmcode vermuten sollten wir versuchen so viele Testf\u00e4lle zu finden, sodass alle diese Fallunterscheidungen abgelaufen werden. Interessant sind beim Eingeben immer Sonderf\u00e4lle bzw. Grenzen von Wertebereichen. (Unsere Methode gibt da nicht viel her, aber wenn wir etwa eine Substring-Funktion haben, lassen sich schnell viele Methoden\u00fcbergaben finden, die interessant sind.)<\/p>\n<p>com\/tutego\/insel\/junit\/util\/StringUtilsTest.java<\/p>\n<pre>package com.tutego.insel.junit.util;\r\n<strong>import static org.junit.Assert.*;\r\n<\/strong>import org.junit.Test;\r\npublic class StringUtilsTest\r\n{\r\n<strong> @Test\r\n<\/strong> public void <strong>testReverse()\r\n<\/strong> {\r\n<strong>  assertEquals( \"\", StringUtils.reverse( \"\" ) );\r\n<\/strong><strong>  assertEquals( \"cba\", StringUtils.reverse( \"abc\" ) );\r\n<\/strong> }\r\n}<\/pre>\n<p>Die Klasse zeigt vier Besonderheiten.<\/p>\n<ol>\n<li>Die Methode, die sich einzelne Szenarien vornehmen und Klassen\/Methoden testen, tragen die Annotation @Test.<\/li>\n<li>Eine \u00fcbliche Namenskonvention (obwohl nicht zwingend n\u00f6tig) ist, dass die Methode, die den Test enth\u00e4lt mit dem Pr\u00e4fix \u201etest\u201c beginnt und mit dem Namen der Methode endet, die sie testet. Da in unserem Fall die Methode reverse() getestet wird, hei\u00dft die Testmethode dementsprechend \u201etestReverse\u201c. Eine testXXX()-Methode liefert nie eine R\u00fcckgabe. Testmethoden k\u00f6nnen auch ganze Szenarien testen, die nicht unbedingt an einer Methode festzumachen sind, aber hier testest testReverse() nur die reverse()-Methode.<\/li>\n<li>JUnit bietet eine Reihe von assertXXX()-Methoden, die den erwarteten Zustand mit dem Ist-Zustand vergleichen; gibt es Abweichungen folgt eine Ausnahme. assertEquals() nimmt einen equals()-Vergleich der beiden Objekte vor. Wenn demnach StringUtils.reverse(&#8222;&#8220;) die leere Zeichenkette &#8222;&#8220; liefert, ist alles in Ordnung und der Test wird fortgesetzt.<\/li>\n<li>Der statische Import aller statischen Eigenschaften der Klasse org.junit.Assert k\u00fcrzt die Schreibweise ab, sodass im Programm statt Assert.assertEquals() nur assertEquals() geschrieben werden kann.<\/li>\n<\/ol>\n<h5>1.2.3 JUnit-Tests ausf\u00fchren<\/h5>\n<p>In einer Entwicklungsumgebung l\u00e4sst sich die Testausf\u00fchrung leicht ablaufen. Eclipse zeigt zum Beispiel die Ergebnisse in der JUnit-View an und bietet mit einem gr\u00fcnen bzw. roten Balken direktes visuelles Feedback.<\/p>\n<p>Nat\u00fcrlich lassen sich die Tests auch von Kommandozeile ausf\u00fchren, obwohl das selten ist, denn in der Regel werden die Tests im Zuge eines Build-Prozess \u2013 den etwa Ant steuert \u2013 angesto\u00dfen. Wer das dennoch \u00fcber die Kommandozeile machen m\u00f6chte, schreibt:<\/p>\n<p>com\/tutego\/insel\/junit\/RunTest.java, main()<\/p>\n<pre>JUnitCore.main( StringUtilsTest.class.getName() );<\/pre>\n<h5>1.2.4 assertXXX()-Methoden der Klasse Assert<\/h5>\n<p>Assert ist die Klasse mit den assertXXX()-Methoden, die immer dann einen AssertionError ausl\u00f6sen, wenn ein aktueller Wert nicht so wie der gew\u00fcnschte war. Der JUnit-Runner registriert alle AssertionError und speichert sie f\u00fcr die Statistik. Bis auf drei Ausnahmen beginnen alle Methoden der Klasse Assert mit dem Pr\u00e4fix \u201eassert\u201c \u2013 zwei andere hei\u00dfen fail() und eine isArray() \u2013 und alle Methoden gibt es einmal mit einer Testmeldung, die beim AssertionError dann erscheint, und einmal ohne, wenn JUnit keine extra Meldung angeben soll.<\/p>\n<p>Eigentlich reicht zum Testen die Methode assertTrue(boolean condition) aus. Ist die Bedingung wahr, so ist alles in Ordnung. Wenn nicht, gibt es den AssertionError.<\/p>\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td valign=\"top\" width=\"645\">org.junit.<strong>Assert<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<ul>\n<li>static void assertTrue( boolean condition )<\/li>\n<li>static void assertTrue( String message, boolean condition )<\/li>\n<li>static void assertFalse( boolean condition )<\/li>\n<li>static void assertFalse( String message, boolean condition )<\/li>\n<\/ul>\n<p>Um es Entwicklern etwas komfortabler zu machen, bietet JUnit sechs Kategorien von Hilfsmethoden. Zun\u00e4chst sind es assertNull() und assertNotNull(), die testen, ob das Argument null bzw. nicht null ist. Ein Aufruf von assertNull(Object object) ist dann nichts anderes als assertTrue(object == null).<\/p>\n<ul>\n<li>static void assertNotNull( Object object )<\/li>\n<li>static void assertNotNull( String message, Object object )<\/li>\n<li>static void assertNull( Object object )<\/li>\n<li>static void assertNull( String message, Object object )<\/li>\n<\/ul>\n<p>Die n\u00e4chste Kategorie testet, ob das Objekt identisch (nicht equals()-gleich) mit einem anderen Objekt ist.<\/p>\n<ul>\n<li>static void assertNotSame( Object unexpected, Object actual)<\/li>\n<li>static void assertNotSame( String message, Object unexpected, Object actual)<\/li>\n<li>static void assertSame( Object expected, Object actual)<\/li>\n<li>static void assertSame( String message, Object expected, Object actual)<\/li>\n<\/ul>\n<p>Statt einem Referenztest f\u00fchren die folgenden Methoden einen equals()-Vergleich durch:<\/p>\n<ul>\n<li>static void assertEquals( Object expected, Object actual )<\/li>\n<li>static void assertEquals( String message, Object expected, Object actual )<\/li>\n<\/ul>\n<p>Zum Testen von primitiven Datentypen gibt es nur zwei Methoden. (Das reicht, denn zum einen werden anderen primitive Typen automatisch typangepasst, zum zweiten kommt Boxing dann ins Spiel, sodass assertEquals(Object, Object) wieder passt.<\/p>\n<ul>\n<li>static void assertEquals( long expected, long actual )<\/li>\n<li>static void assertEquals( String message, long expected, long actual )<\/li>\n<li>static void assertEquals( double expected, double actual, double delta )<\/li>\n<li>static void assertEquals( String message, double expected, double actual, double delta )<\/li>\n<\/ul>\n<p>Flie\u00dfkommazahlen bekommen bei assertEquals() einen Delta-Wert, in dem sich das Ergebnis bewegen muss. Das tr\u00e4gt der Tatsache Rechnung, dass vielleicht in der Bildschirmausgabe zwei Zahlen gleich sind, jedoch nicht bitweise gleich sind, da sich kleine Rechenfehler <a name=\"OLE_LINK1\"><\/a>akkumuliert haben. Sind jedoch die Flie\u00dfkommazahlen in einem Wrapper, also etwa Double verpackt, leitet ja assertEquals() den Test nur an die equals()-Methode der Wrapper-Klasse weiter, die nat\u00fcrlich kein Delta ber\u00fccksichtigt.<\/p>\n<p>Als letztes folgen Methoden, die Felderinhalte vergleichen:<\/p>\n<ul>\n<li>static void assertArrayEquals( byte[] expecteds, byte[] actuals )<\/li>\n<li>static void assertArrayEquals( String message, byte[] expecteds, byte[] actuals )<\/li>\n<li>static void assertArrayEquals( char[] expecteds, char[] actuals )<\/li>\n<li>static void assertArrayEquals( String message, char[] expecteds, char[] actuals )<\/li>\n<li>static void assertArrayEquals( int[] expecteds, int[] actuals )<\/li>\n<li>static void assertArrayEquals( String message, int[] expecteds, int[] actuals )<\/li>\n<li>static void assertArrayEquals( long[] expecteds, long[] actuals )<\/li>\n<li>static void assertArrayEquals( Object[] expecteds, Object[] actuals )<\/li>\n<li>static void assertArrayEquals( String message, long[] expecteds, long[] actuals )<\/li>\n<li>static void assertArrayEquals( String message, Object[] expecteds, Object[] actuals )<\/li>\n<li>static void assertArrayEquals( short[] expecteds, short[] actuals )<\/li>\n<li>static void assertArrayEquals( String message, short[] expecteds, short[] actuals )<\/li>\n<\/ul>\n<h5>1.2.5 Matcher-Objekte und Hamcrest<\/h5>\n<p>Eine Sonderrolle nehmen zwei assertThat()-Methoden ein. Sie erm\u00f6glichen es, Tests deklarativer zu schreiben, so dass sie sich wie englische S\u00e4tze lesen lassen. Stellen wir bei einigen Beispielen assertThat() und assertEquals() bei den gleichen Aufgaben gegen\u00fcber.<\/p>\n<table border=\"1\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td valign=\"top\" width=\"404\"><strong>assertXXX() ohne Matcher<\/strong><\/td>\n<td valign=\"top\" width=\"446\"><strong>assertThat()<\/strong><\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"404\">assertNotNull(new Object());<\/td>\n<td valign=\"top\" width=\"446\">assertThat(new Object(), is( notNullValue() ));<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"404\">assertEquals(&#8222;&#8220;, StringUtils.reverse( &#8222;&#8220; ));<\/td>\n<td valign=\"top\" width=\"446\">assertThat(&#8222;&#8220;, is( equalTo( StringUtils.reverse( &#8222;&#8220; ) ) ));<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"404\">assertSame(&#8222;&#8220;, &#8222;&#8220;);<\/td>\n<td valign=\"top\" width=\"446\">assertThat(&#8222;&#8220;, is( sameInstance( &#8222;&#8220; ) ));<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"404\">assertNotSame(&#8222;&#8220;, &#8222;a&#8220;);<\/td>\n<td valign=\"top\" width=\"446\">assertThat(&#8222;a&#8220;, is( not( sameInstance( &#8222;&#8220; ) ) ));<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Zun\u00e4chst f\u00e4llt auf, dass als erstes Argument bei assertThat() den Wert beschreibt den wir haben, dieser aber bei den sonstigen assertXXX()-Methoden erst immer hinter dem erwarteten Wert folgt.<\/p>\n<p>Die allgemeine Syntax von assertThat() ist folgende.<\/p>\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td valign=\"top\" width=\"645\">org.junit.<strong>Assert<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<ul>\n<li>static &lt;T&gt; void assertThat( T actual, org.hamcrest.Matcher&lt;T&gt; matcher )<\/li>\n<li>static &lt;T&gt; void assertThat( String reason, T actual, org.hamcrest.Matcher&lt;T&gt; matcher )<\/li>\n<\/ul>\n<p>Ob der Test korrekt ist oder nicht, entscheidet ein org.hamcrest.Matcher-Objekt. An dem Paket org.hamcrest l\u00e4sst sich schon ablesen, dass nicht JUnit auf die Idee kam, sondern auf eine Bibliothek namens <em>Hamcrest<\/em> zur\u00fcckgreift, die unter <em>http:\/\/code.google.com\/p\/hamcrest\/<\/em> gehostet ist. Um daher die volle Funktionalit\u00e4t von Hamcrest zu nutzen, sollte von der Webseite das Java-Archiv <em>hamcrest-all-1.2.jar<\/em> geladen und im Klassenpfad eingebunden werden. (<em>hamcrest-core-1.2.jar<\/em> ist eine Alternative, enth\u00e4lt aber nur die grundlegenden Klassen und ist nicht so n\u00fctzlich.)<\/p>\n<p>Da die Matcher \u00fcber eine clevere Art verschachtelt werden, lesen sich die assertThat()-Aufrufe wie S\u00e4tze. is() hat funktional keine Bedeutung, l\u00e4sst die Aussage aber noch \u201eenglischer\u201c werden. Aber wo sind bei einem Aufruf wie assertThat(&#8222;&#8220;, not(sameInstance(&#8222;a&#8220;))); die Objekte? Zun\u00e4chst gilt, dass die \u201eW\u00f6rter\u201c statische Methoden der Klasse org.hamcrest.CoreMatchers sind. Statisch eingebunden ergibt sich die kurze Schreibweise, die sonst lauten w\u00fcrde: CoreMatchers.not(CoreMatchers.sameInstance(&#8222;a&#8220;)). Die Matcher-Objekte sind einfach R\u00fcckgaben der statischen Methoden. Wir h\u00e4tten auch schreiben k\u00f6nnen: new IsNot&lt;String&gt;(new IsSame&lt;String&gt;(&#8222;a&#8220;)) und das ist unser Matcher-Objekt. Matcher-Objekte besitzen eine Methode boolean matches(Object item), die letztendlich den Test durchf\u00fchrt und assertThat() sagt, ob eine Ausnahme ausgel\u00f6st werden muss, weil ein Fehler auftrat.<\/p>\n<p>Die Methoden is(), isInstance(), not(), notNullValue(), equalTo() sind nicht die einzigen aus CoreMatchers. Die folgende \u00dcbersicht z\u00e4hlt die aktuellen statischen Methoden auf:<\/p>\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td valign=\"top\" width=\"645\">org.hamcrest.<strong>CoreMatchers<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<ul>\n<li>static &lt;T&gt; Matcher&lt;T&gt; allOf( Iterable&lt;Matcher&lt;? extends T&gt;&gt; matchers )<\/li>\n<li>static &lt;T&gt; Matcher&lt;T&gt; allOf( Matcher&lt;? extends T&gt;&#8230; matchers )<\/li>\n<li>static &lt;T&gt; Matcher&lt;T&gt; any( Class&lt;T&gt; type )<\/li>\n<li>static &lt;T&gt; Matcher&lt;T&gt; anyOf( Iterable&lt;Matcher&lt;? extends T&gt;&gt; matchers )<\/li>\n<li>static &lt;T&gt; Matcher&lt;T&gt; anyOf( Matcher&lt;? extends T&gt;&#8230; matchers )<\/li>\n<li>static &lt;T&gt; Matcher&lt;T&gt; anything()<\/li>\n<li>static &lt;T&gt; Matcher&lt;T&gt; anything( String description )<\/li>\n<li>static &lt;T&gt; Matcher&lt;T&gt; describedAs( String description, Matcher&lt;T&gt; matcher, Object&#8230; values )<\/li>\n<li>static &lt;T&gt; Matcher&lt;T&gt; equalTo( T operand )<\/li>\n<li>static Matcher&lt;Object&gt; instanceOf( Class&lt;?&gt; type )<\/li>\n<li>static Matcher&lt;Object&gt; is( Class&lt;?&gt; type )<\/li>\n<li>static &lt;T&gt; Matcher&lt;T&gt; is( Matcher&lt;T&gt; matcher )<\/li>\n<li>static &lt;T&gt; Matcher&lt;T&gt; is( T value )<\/li>\n<li>static &lt;T&gt; Matcher&lt;T&gt; not( Matcher&lt;T&gt; matcher )<\/li>\n<li>static &lt;T&gt; Matcher&lt;T&gt; not( T value )<\/li>\n<li>static &lt;T&gt; Matcher&lt;T&gt; notNullValue()<\/li>\n<li>static &lt;T&gt; Matcher&lt;T&gt; notNullValue( Class&lt;T&gt; type )<\/li>\n<li>static &lt;T&gt; Matcher&lt;T&gt; nullValue()<\/li>\n<li>static &lt;T&gt; Matcher&lt;T&gt; nullValue( Class&lt;T&gt; type )<\/li>\n<li>static &lt;T&gt; Matcher&lt;T&gt; sameInstance( T object )<\/li>\n<\/ul>\n<h6>Vorteile von Matcher-Objekten<\/h6>\n<p>Stellen wir noch einmal<\/p>\n<ul>\n<li>assertEquals( &#8222;&#8220;, StringUtils.reverse( &#8222;&#8220; ) ); und<\/li>\n<li>assertThat( &#8222;&#8220;, is( equalTo( StringUtils.reverse( &#8222;&#8220; ) ) ) );<\/li>\n<\/ul>\n<p>gegen\u00fcber. Ist assertThat() die bessere Alternative? Nicht wirklich, denn es gibt keinen Nutzen, wenn assertThat() exakt das \u00fcbernimmt, was die assertXXX()-Methode macht, also in unserem Fall ein Gleichheitstest. assertThat() ist dann sogar l\u00e4nger.<\/p>\n<p>Interessant wird assertThat() aus zwei Gr\u00fcnden:<\/p>\n<p>1. Es gibt eine gro\u00dfe Sammlung an Matcher-Objekten, die einem Programmierer viel Arbeit abnehmen. Wenn es zu pr\u00fcfen gilt, ob die zu testende Methode eine Datei mit genau 1111 Bytes angelegt hat, so m\u00fcsste das in Java etwa new File(file).exists() &amp;&amp; new File(file).length() == 1111 lauten. Mit entsprechenden Hamcrest-Matchern<a href=\"#_ftn1_1629\" name=\"_ftnref1_1629\">[1]<\/a> hei\u00dft es dann kompakt:<br \/>\nallOf(exists(), sized((Matcher&lt;Long&gt;)equalTo(0L))).<a href=\"#_ftn2_1629\" name=\"_ftnref2_1629\">[2]<\/a> Ein zweites Beispiel betrifft Mengenabfragen. Die praktische Methode hasItems() zum Beispiel testet, ob Elemente in einer Collection sind; ohne Matcher w\u00e4re der Test in Java mehr Schreibarbeit.<\/p>\n<p>2. Die assertEquals()-Methode l\u00e4uft entweder durch, oder bricht mit einer Exception ab, was den Test dann beendet. Wir bekommen beim Abbruch dann den Hinweis, dass der gew\u00fcnschte Wert nicht mit dem berechneten \u00fcbereinstimmt, aber wo genau der Fehler ist, f\u00e4llt weniger auf. assertThat() liefert ausgezeichnete Fehlermeldungen.<\/p>\n<p>Folgendes Beispiel fasst die beiden Vorteile zusammen. Stellen wir uns vor, wir haben eine Datenstruktur (in dem Beispiel eine ArrayList). Sie kann Elemente auch entfernen und das ist genau die Methode removeAll(), dessen Funktionalit\u00e4t wir testen wollen.<\/p>\n<pre>ArrayList&lt;String&gt; list = new ArrayList&lt;String&gt;();\r\nCollections.addAll( list, \"a\", \"b\", \"c\", \"d\", \"e\" );\r\nlist.removeAll( Arrays.asList( \"b\", \"d\" ) );<\/pre>\n<p>Wie kann ein Test aussehen? Ein Test k\u00f6nnte schauen, ob die Gr\u00f6\u00dfe der Liste von 5 Elementen auf 3 kommt, wenn die beiden Elemente \u201eb\u201c und \u201ed\u201c gel\u00f6scht werden. Und der Test kann pr\u00fcfen, ob b und d wirklich entfernt wurde, aber \u201ea\u201c, \u201ec\u201c und \u201ee\u201c weiterhin in der Liste sind. Nach dem Import von<\/p>\n<pre>import static org.hamcrest.CoreMatchers.*;\r\nimport static org.hamcrest.collection.IsCollectionWithSize.hasSize;<\/pre>\n<p>l\u00e4sst sich genau das \u00fcber assertThat() testen:<\/p>\n<pre>assertThat( list, hasSize(3) );\r\nassertThat( list, both( hasItems( \"a\", \"c\", \"e\" ) ).and( not( hasItems( \"b\", \"d\" ) ) ) );<\/pre>\n<p>Die Methode hasSize() pr\u00fcft die Gr\u00f6\u00dfe der Liste, hasItems() testet, ob Elemente in der Datenstruktur sind. Die Kombination both().and() pr\u00fcft zwei Bedingungen, die beide erf\u00fcllt sein m\u00fcssen. Alternativ w\u00e4re auch allOf() m\u00f6glich.<\/p>\n<p>So machen diese beiden Zeilen den ersten Punkt der beiden Vorteile f\u00fcr Hamcrest-Matcher deutlich.<\/p>\n<p>Der zweite Vorteil waren die Meldungen im Fehlerfall. \u00c4ndern wir zum Test das erste hasItems() in hasItems(&#8222;_&#8220;, &#8222;c&#8220;, &#8222;e&#8220;). Der Testlauf wird dann nat\u00fcrlich einen Fehler geben. Die Meldung ist (etwas einger\u00fcckt):<\/p>\n<pre>Expected: (\r\n(a collection containing \"_\" and a collection containing \"c\" and a collection containing \"e\")\r\nand not\r\n(a collection containing \"b\" and a collection containing \"d\")\r\n)\r\ngot: &lt;[a, c, e]&gt;<\/pre>\n<p>Der Hinweis \u201egot: &lt;[a, c, e]&gt;\u201c gilt f\u00fcr den ersten Matcher und nicht wie sonst \u00fcblich bei assertEquals() f\u00fcr den gesamten Ausdruck. So sind die Aussagen deutlicher lokaler und nicht wie bei Anfragen, die auf assertTrue() zur\u00fcckgehen, einfach nur, dass false kam, aber true erwartet wurde.<\/p>\n<h5>1.2.6 Exceptions testen<\/h5>\n<p>W\u00e4hrend der Implementierung fallen oft Dinge auf, die die eigentliche Implementierung noch nicht ber\u00fccksichtigt. Dann sollte sofort diese neu gewonnene Erkenntnis im Testfall einflie\u00dfen. In unserem Beispiel soll das bedeuten, dass bisher nicht wirklich gekl\u00e4rt ist, was bei einem null-Argument passieren soll. Bisher gibt es eine NullPointerExcpetion und das ist auch v\u00f6llig in Ordnung, aber in einem Testfall steht das bisher nicht, dass auch wirklich eine NullPointerExcpetion folgt. Diese Fragestellung betont eine gern vergessene Seite des Testens, denn Testautoren d\u00fcrfen sich nicht nur darauf konzentrieren, was die Implementierung denn so alles richtig machen soll, der Test muss auch kontrollieren, ob im Fehlerfall auch dieser korrekt gemeldet wird. Wenn es nicht in der Spezifikation steht d\u00fcrfen auf keinen Fall falsche Werte gerade geb\u00fcgelt werden: falsche Werte m\u00fcssen immer zu einer Ausnahme oder einem wohl definierten Verhalten f\u00fchren.<\/p>\n<p>Wir wollen unser Beispiel so erweitern, dass reverse(null) eine IllegalArgumentException ausl\u00f6st. Auf zwei Arten l\u00e4sst sich testen, ob die erwartete IllegalArgumentException auch wirklich kommt. Die erste Variante:<\/p>\n<p>com\/tutego\/insel\/junit\/utils\/StringUtilsTest.java, Ausschnitt<\/p>\n<pre>try\r\n{\r\n StringUtils.reverse( null );\r\n<strong> fail( \"reverse(null) should throw IllegalArgumentException\" );\r\n<\/strong>}\r\ncatch ( IllegalArgumentException e ) { }<\/pre>\n<p>F\u00fchrt reverse(null) zur Ausnahme, was ja gewollt ist, wird der catch-Block die IllegalArgumentException einfach auffangen, ignorieren und dann geht es in der Testfunktion mit anderen Dingen weiter. Sollte keine Ausnahme folgen, so wird die Anweisung nach dem reverse()-Aufruf ausgef\u00fchrt, und die ist fail(). Diese Methode l\u00f6st eine JUnit-Ausnahme mit einer Meldung aus und signalisiert dadurch, dass im Test etwas nicht stimmte. Allerdings bleibt ein Problem: Was ist, wenn zwar eine Ausnahme ausgel\u00f6st wird, aber eine Falsche! Eine nicht-Runtime-Exeception kann es nicht sein, dann da w\u00fcrde der Compiler uns zum catch-Block zwingen. Aber was w\u00e4re mit einer anderen RuntimeException, etwa der NullPointerException? Diese w\u00fcrde zwar von JUnit abgefangen werden und JUnit w\u00fcrde einen Fehler melden, aber dann ist nicht abzulesen, was das f\u00fcr ein Fehler ist und welche Rolle er spielt. Eine L\u00f6sung w\u00e4re, noch einen catch-Block anzuh\u00e4ngen und fail() aufzurufen, doch das w\u00fcrde erst einmal Quellcodeduplizierung bedeuten. Daher bietet JUnit eine andere elegante Variante \u2013 die Annotation @Test wird parametrisiert.<\/p>\n<p>com\/tutego\/insel\/junit\/util\/StringUtilsTest.java, testReverseException()<\/p>\n<pre>@Test( <strong>expected = IllegalArgumentException.class<\/strong> )\r\npublic void testReverseException()\r\n{\r\n StringUtils.reverse( null );\r\n}<\/pre>\n<p>JUnit erwartet (engl. expected) eine IllegalArgumentException. Folgt sie nicht, meldet JUnit das als Fehler. Der Vorteil gegen\u00fcber der fail()-Variante ist die K\u00fcrze, ein Nachteil der, dass dann unter Umst\u00e4nden mehrere testXXX()-Methoden f\u00fcr eine zu testende Methode n\u00f6tig sind. Wir haben hier eine zweite Methode testReverseException() hinzugenommen. (Der reverse(null)-Test h\u00e4tte auch am Ende der ersten Methode stehen k\u00f6nnen, dann m\u00fcssen wir aber sichergehen, dass dieser Exception-Test am Ende durchgef\u00fchrt wird.<\/p>\n<h5>1.2.7 Tests ignorieren und Grenzen f\u00fcr Ausf\u00fchrungszeiten festlegen<\/h5>\n<p>Durch Umstrukturierung von Quellcode kann es sein, dass Testcode nicht l\u00e4nger g\u00fcltig ist und entfernt oder umgebaut werden muss. Damit der Testfall nicht ausgef\u00fchrt wird, muss er nicht auskommentiert werden (das bringt den Nachteil mit sich, dass das Refactoring etwa im Zuge einer Umbenennung von Bezeichnern sich nicht auf auskommentierte Bereiche auswirkt.) Stattdessen wird einfach eine weitere Annotation @Ignore an die Methode gesetzt:<\/p>\n<pre><strong>@Ignore\r\n<\/strong>@Test\r\npublic void testReverse()<\/pre>\n<p>Nach gro\u00dfen Refactorings kann die Software funktional noch laufen, aber vielleicht viel langsamer geworden sein. Dann stellt sich die Frage, ob das im Sinne des Anforderungskataloges noch korrekt ist, wenn ein performantes Programm nach einer \u00c4nderung l\u00e4uft wie eine Schnecke. Um Laufzeitver\u00e4nderungen als G\u00fcltigkeitskriterium einzuf\u00fchren, kann die Annotation @Test ein timeout in Millisekunden angeben.<\/p>\n<pre>@Test( <strong>timeout = 500<\/strong> )\r\npublic void test()<\/pre>\n<p>Wird die Testmethode dann nicht innerhalb der Schranke ausgef\u00fchrt, gilt das als versagter Test und JUnit meldet einen Fehler.<\/p>\n<h5>1.2.8 Mit Methoden der Assume-Klasse Tests abbrechen<\/h5>\n<p>W\u00e4hrend die assertXXX()-Methoden zu einer Ausnahme f\u00fchren und so anzeigen, dass der Test etwas gefunden hat, was nicht korrekt ist, bietet JUnit mit Asssume.assumeXXX()-Methoden die M\u00f6glichkeit, die Tests nicht fortzuf\u00fchren. Das ist sinnvoll etwa dann, wenn die Testausf\u00fchrung nicht m\u00f6glich ist, wenn etwa der Testrechner keine Grafikkarte hat, das Netzwerk nicht da ist oder das Datensystem voll. Dabei geht es nicht darum, etwa zu testen, wie sich die Routine bei einem nicht vorhandenen Netzwerk verh\u00e4lt \u2013 das gilt es nat\u00fcrlich auch zu testen. Aber steht das Netzwerk nicht, k\u00f6nnen logischerweise auch keine Tests laufen, die das Netzwerk zwingend ben\u00f6tigen.<\/p>\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td valign=\"top\" width=\"645\">org.junit.<strong>Assume<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<ul>\n<li>static void assumeTrue( boolean b )<\/li>\n<li>static void assumeNotNull( Object&#8230; objects )<\/li>\n<li>static void assumeNoException( Throwable t )<\/li>\n<li>static &lt;T&gt; void assumeThat( T actual, org.hamcrest.Matcher&lt;T&gt; matcher )<\/li>\n<\/ul>\n<p>Die assumeXXX()-Methoden f\u00fchren zu keiner Ausnahme, brechen die Testausf\u00fchrung aber ab.<\/p>\n<h4>1.3 Wie gutes Design das Testen erm\u00f6glicht<\/h4>\n<p>Statische Methoden nach dem Muster: Parameter liefert die Eingabe, der R\u00fcckgabewert das Ergebnis, sind einfach zu testen. Sie ver\u00e4ndern keine Umgebung und Zust\u00e4nde gibt es keine. Der Testfall muss lediglich die R\u00fcckgabe untersuchen und das ist einfach. Aufw\u00e4ndiger wird es dann schon, wenn Dinge getestet werden wollen, die aufw\u00e4ndige System\u00e4nderungen nach sich ziehen: Wurde eine Datei angelegt? Stehen Dinge in der Datenbank wie gew\u00fcnscht? Hat der Cluster die Daten auf andere Server gespiegelt? Liefert ein externes Programm die R\u00fcckgabe wie erwartet? Gibt eine native Methode tats\u00e4chlich das zur\u00fcck was sie verspricht und zieht nicht die JVM ins Grab?<\/p>\n<p>Sind Dinge pl\u00f6tzlich nicht mehr testbar, offenbart das im Allgemeinen ein schwaches Design. H\u00e4ufig liegt es daran, dass eine Klasse zu viele Verantwortlichkeiten hat. Als Beispiel wollen wir uns eine Klasse ansehen, die Visitenkarten im vCard-Format (Dateiendung <em>vcf<\/em>) schreibt.<a href=\"#_ftn3_1629\" name=\"_ftnref3_1629\">[3]<\/a> Um den Quellcode schlank zu halten verzichtet die Klasse VCard auf Setter\/Getter.<\/p>\n<p>com\/tutego\/insel\/junit\/util\/vdf\/v1\/VCard.java, VCard<\/p>\n<pre>public class VCard\r\n{\r\n<span style=\"font-size: 14px;\">public String formattedName;\r\n<\/span><span style=\"font-size: 14px;\">public String email;\r\n<\/span>public void export( String filename ) throws IOException\r\n{\r\nStringBuilder result = new StringBuilder( \"BEGIN:VCARD\\n\" );\r\nif ( formattedName != null &amp;&amp; ! formattedName.isEmpty() )\r\nresult.append( \"FN:\" ).append( formattedName ).append( \"\\n\" );\r\nif ( email != null &amp;&amp; ! email.isEmpty() )\r\nresult.append( \"EMAIL:\" ).append( email ).append( \"\\n\" );\r\nnew FileWriter( filename ).append( result.append( \"END:VCARD\"\r\n).toString() ).close();\r\n}\r\n}<\/pre>\n<p>Wenn die Klasse etwa die Variable formattedName auf &#8222;Powerpuff Girls&#8220; steht und email auf &#8222;powerpuff@townsville.com\u201c, dann w\u00fcrde die Methode export() eine Datei erstellen mit dem folgenden Inhalt:<\/p>\n<pre>BEGIN:VCARD\r\nFN:Powerpuff Girls\r\nEMAIL:powerpuff@townsville.com\r\nEND:VCARD<\/pre>\n<p>Hauptaufgabe der Klasse ist die korrekte Erstellung des Ausgabeformates nach dem vCard-Standard. Die Klasse l\u00e4sst sich grunds\u00e4tzlich testen, aber der Test wird nicht besonders sch\u00f6n. Zun\u00e4chst m\u00fcssten unterschiedliche vCard-Eigenschaften gesetzt werden, dann die vCard in eine Datei geschrieben, anschlie\u00dfend die Datei ge\u00f6ffnet, den Inhalt ausgelesen und zum Schluss auf Korrektheit untersucht werden. Das ist kein sympathischer Weg! Die Klasse VCard ist nicht testorientiert entworfen worden. Warum? Neben der Tatsache, dass so ein Test wegen der Dateizugriffe recht lange dauern k\u00f6nnte, l\u00e4sst sich prinzipiell festhalten, dass die Methode export() zwei Verantwortlichkeiten verbindet, n\u00e4mlich die Ausgabe in dem speziellen vCard-Format und die Ausgabe in eine Datei. St\u00fcnde das Prinzip TDD hinter dem Entwurf, so h\u00e4tte der Autor die Anteile Format und Ausgabe getrennt. Denn g\u00e4be es eine eigene Methode zur Aufbereitung der Dateien etwa in einem String, so m\u00fcsste der Test nur diese Methode aufrufen und br\u00e4uchte nicht in einen String schreiben. Verbessern wir die Klasse:<\/p>\n<p>com\/tutego\/insel\/junit\/util\/vdf\/v2\/VCard.java, VCard<\/p>\n<pre>public class VCard\r\n{\r\npublic String formattedName;\r\npublic String email;\r\npublic <strong>void export( Writer out )<\/strong> throws IOException\r\n{\r\nout.write( <strong>toString()<\/strong> );\r\n}\r\npublic <strong>void export( String filename )<\/strong> throws IOException\r\n{\r\nFileWriter writer = new FileWriter( filename );\r\n<strong>export( writer );\r\n<\/strong>writer.close();\r\n}\r\n@Override\r\npublic <strong>String toString()\r\n<\/strong>{\r\nStringBuilder result = new StringBuilder( \"BEGIN:VCARD\\n\" );\r\nif ( formattedName != null &amp;&amp; ! formattedName.isEmpty() )\r\nresult.append( \"FN:\" ).append( formattedName ).append( \"\\n\" );\r\nif ( email != null &amp;&amp; ! email.isEmpty() )\r\nresult.append( \"EMAIL:\" ).append( email ).append( \"\\n\" );\r\nreturn result.append( \"END:VCARD\" ).toString();\r\n}\r\n}<\/pre>\n<p>Die Variante bringt gleich zwei Verbesserungen mit sich:<\/p>\n<p>a) Die Methode toString() liefert nun den nach dem vCard-Standard aufbereiteten String. Der Test muss nun lediglich ein VCard-Objekt aufbauen, die Variablen setzen, toString() aufrufen und ohne Dateioperationen den String auf Korrektheit testen. F\u00fcr den Client \u00e4ndert sich die API aber nicht; er schreibt weiterhin export().<\/p>\n<p>b) Direkt in Dateien zu schreiben ist nicht mehr so richtig zeitgem\u00e4\u00df. Das ber\u00fccksichtigt die Klasse und bietet eine \u00fcberladene Version von export() mit einem allgemeinen Writer. Sollte dann etwa eine vCard \u00fcber das Netzwerk verschickt werden, ist das kein Problem und es muss lediglich ein passender Writer f\u00fcr das Netzwerkziel \u00fcbergeben werden. Vorher w\u00e4re das sehr umst\u00e4ndlich gewesen: Datei erzeugen, Datei auslesen, String verschicken.<\/p>\n<p>Im Endeffekt ist der Gewinn gro\u00df. Der Test ist performanter und das Design f\u00fchrt zu besserem Quellcode. Eine Win-Win-Situation.<\/p>\n<p>Der gew\u00e4hlte Ansatz zeigt den Ansatz, wie bei Implementierungen zu Verfahren ist, die insbesondere mit externen Ressourcen sprechen. Diese gilt es soweit wie m\u00f6glich rauszuziehen, wenn n\u00f6tig, auch in einem neuen Typ, der dann als Testimplementierung injiziert werden kann.<\/p>\n<h4>1.4 Aufbau gr\u00f6\u00dferer Testf\u00e4lle<\/h4>\n<h5>1.4.1 Fixtures<\/h5>\n<p>Eine wichtige Eigenschaft von Tests ist, dass sie voneinander unabh\u00e4ngig sind. Die Annahme, dass ein erster Test ein paar Testdaten zum Beispiel anlegt, auf die dann der zweite Test zur\u00fcckgreifen kann ist falsch. Aus dieser Tatsache muss die Konsequenz gezogen werden, dass jede einzelne Testmethode davon ausgehen muss die erste Testmethode zu sein und somit ihren Initialzustand selbst herstellen muss. Es w\u00e4re aber unn\u00f6tige Quellcodeduplizierung, wenn jede Testmethode nun diesen Startzustand selbst aufbaut. Dieser Anfangszustand hei\u00dft <em>Fixture<\/em> (zu Deutsch etwa festes Inventar) und JUnit bietet hier vier Annotationen. Wie sie wirken, zeigt folgendes Beispiel:<\/p>\n<p>com\/tutego\/insel\/junit\/util\/FixtureDemoTest.java, FixtureDemoTest<\/p>\n<pre>public class FixtureDemoTest\r\n{\r\n<strong>@BeforeClass<\/strong> public static void beforeClass()\r\n{\r\nSystem.out.println( \"@BeforeClass\" );\r\n}\r\n<strong>@AfterClass<\/strong> public static void afterClass()\r\n{\r\nSystem.out.println( \"@AfterClass\" );\r\n}\r\n<strong>@Before<\/strong> public void setUp()\r\n{\r\nSystem.out.println( \"@Before\" );\r\n}\r\n<strong>@After<\/strong> public void tearDown()\r\n{\r\nSystem.out.println( \"@After\" );\r\n}\r\n<strong>@Test<\/strong> public void test1()\r\n{\r\nSystem.out.println( \"test 1\" );\r\n}\r\n<strong>@Test<\/strong> public void test2()\r\n{\r\nSystem.out.println( \"test 2\" );\r\n}\r\n}<\/pre>\n<p>Die Annotationen beziehen sich auf zwei Anwendungsf\u00e4lle:<\/p>\n<ul>\n<li>@BeforeClass, @AfterClass: Annotiert statische Methoden, die einmal aufgerufen werden, wenn die Klasse f\u00fcr den Test initialisiert wird bzw. wenn alle Tests f\u00fcr die Klasse abgeschlossen sind.<\/li>\n<li>@Before, @After: Annotiert Objektmethoden, die immer vor bzw. nach einer Testmethode aufgerufen werden.<\/li>\n<\/ul>\n<p>L\u00e4uft unser Beispielprogramm ist die Ausgabe daher wie folgt:<\/p>\n<pre>@BeforeClass\r\n@Before\r\n<span style=\"font-size: 14px;\">test 1\r\n<\/span>@After\r\n@Before\r\ntest 2\r\n@After\r\n@AfterClass<\/pre>\n<p>In die @BeforeClass-Methoden wird \u00fcblicherweise das reingesetzt, was teuer im Aufbau ist, etwa eine Datenbankverbindung. Die Ressourcen werden dann in der symmetrischen Methode @AfterClass wieder freigegeben, also zum Beispiel Datenbankverbindungen wieder geschlossen. Da nach einem Test keine Artefakte vom Testfall bleiben sollen f\u00fchren gute @AfterClass\/@After-Methoden sozusagen ein Undo durch.<\/p>\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td valign=\"top\" width=\"658\">Beispiel Setzt ein System.setProperty() \u201eglobale\u201c Zust\u00e4nde, oder \u00fcberschreibt es vordefinierte Properties, so ist @BeforeClass eine guter Zeitpunkt, einen Snapshot zu nehmen und diesen sp\u00e4ter bei @AfterClass wieder herzustellen.<\/p>\n<pre>private static String oldValue;\r\n@BeforeClass public static void beforeClass()\r\n{\r\noldValue = System.getProperty( \"property\" );\r\nSystem.setProperty( \"property\", \"newValue\" );\r\n}\r\n@AfterClass public static void afterClass()\r\n{\r\nif ( oldValue != null )\r\n{\r\nSystem.setProperty( \"property\", oldValue );\r\noldValue = null;\r\n}\r\n}<\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h5>1.4.2 Sammlungen von Testklassen und Klassenorganisation<\/h5>\n<p>Werden die Tests mehr, stellt sich die Frage nach der optimalen Organisation. Als praktikabel hat sich erwiesen, die Testf\u00e4lle in das gleiche Paket wie die zu testenden Klassen zu setzen, aber den Quellcode physikalisch zu trennen. Entwicklungsumgebungen bieten hierzu Konzepte, etwa Eclipse unterschiedliche <em>src<\/em>-Order, die zu Klassen im gleichen Klassenpfad f\u00fchren, dennoch in der IDE visuell getrennt sind. Der Vorteil von Klassen im gleichen Paket ist, das oftmals die Paketsichtbarkeit ausreicht und nicht vorher private Eigenschaften nur f\u00fcr Tests \u00f6ffentlich gemacht werden m\u00fcssen.<\/p>\n<p>Wenn es nun f\u00fcr jedes Paket ein Spiegel-Paket mit den Testklassen gibt, wie werden all diese Testsklassen eines Pakets in einem Rutsch ausgef\u00fchrt? Der Trick besteht im Aufbau einer Test-Suite:<\/p>\n<p>com\/tutego\/insel\/junit\/util\/TestPackage.java, TestPackage<\/p>\n<pre><strong>@RunWith( Suite.class )\r\n<\/strong><strong>@Suite.SuiteClasses(\r\n<\/strong><strong>{\r\n<\/strong><strong>StringUtilsTest.class, FixtureDemoTest.class\r\n<\/strong><strong>} )\r\n<\/strong>public class PackageTest { }<\/pre>\n<p>Die Testklasse ist im Rumpf leer und z\u00e4hlt lediglich \u00fcber Suite.SuiteClasses die Klassen aus.<\/p>\n<p>\u00dcblicherweise besteht ein Projekt aus mehreren hierarchischen Paketen. Wie ist es dann m\u00f6glich, das ganze Projekt mit allen Pakten zu testen und nicht nur ein Paket? Hier hilft uns die Tatsache, dass eine Suite selbst eine Art Testfall ist, die bei @Suite.SuiteClasses angegeben werden darf. Das hei\u00dft praktisch: In jedes Paket wird eine Suite definiert, die alle Testklassen aber auch die Test-Suite der Unterklassen referenziert. In unserem Beispiel hei\u00dft dass, wenn wir in com.tutego.insel.junit.util eine Suite PackageTest hatten, die die beiden TestklassenStringUtilsTest und FixtureDemoTest ansprachen, so setzen wir in das \u00fcbergeordnete Paket com.tutego.insel.junit (ohne util also) ebenfalls eine Suite PackageTest, die dann com.tutego.insel.junit.util.PackageTest referenziert.<\/p>\n<p>com\/tutego\/insel\/junit\/TestPackage.java, TestPackage<\/p>\n<pre>@RunWith( Suite.class )\r\n@Suite.SuiteClasses(\r\n{\r\n<strong>com.tutego.insel.junit.util.PackageTest.class\r\n<\/strong>} )\r\npublic class PackageTest { }<\/pre>\n<p>Der eigentliche Test wird demnach oben in einer Haupthierarchie gestartet und l\u00e4uft rekursiv dann alle Unterpakte ab.<\/p>\n<h4>1.5 Dummy, Fake, Stub und Mock<\/h4>\n<p>Gute objektorientiert entworfene Systeme kennzeichnen sich dadurch, dass es eine hohe Interaktion mit anderen Objekten gibt. Idealerweise zerlegt eine Klasse ein Problem nur bis zu dem Punkt, bis es sich einer anderen Klasse bedienen kann, die dieses einfachere Problem l\u00f6st. Schwierig wird es, wenn eine eigene Klasse auf eine andere komplexe Klasse zur\u00fcckgreift und das Objekt nur dann sinnvoll arbeitet, wenn das referenzierte Objekt da ist, und irgendwie sinnvoll antwortet. Diese Abh\u00e4ngigkeit ist ung\u00fcnstig, denn das Ziel eines guten Test besteht ja darin, lokal zu sein, also die eigentliche Klasse zu testen, und nicht alle referenzierten Klassen um sie herum gleich mit.<\/p>\n<p>In der Praxis begegnen uns drei F\u00e4lle:<\/p>\n<p>\u00b7 <em>Fake-Objekte<\/em>: Sie sind eine g\u00fcltige Implementierung einer Schnittselle, haben aber kein Verhalten, sondern der Rumpf der Methoden ist quasi leer. Sie gibt es nur f\u00fcr die Testf\u00e4lle. Wenn ein Service etwa auf einen anderen Service zur\u00fcckgreift, um eine E-Mail mit der einzigen angebotenen Methode void send(String msg, String receiver) zu versenden, kann ein Fake-Objekt diesen E-Mail-Service \u201eimplementieren\u201c, aber er muss dazu \u00fcberhaupt kein Verhalten nachbilden.<\/p>\n<p>\u00b7 <em>Stub-Objekte<\/em>. Sie implementieren ein bestimmtes Protokoll, sodass sie f\u00fcr den Testfall immer die gleichen Antworten geben k\u00f6nnen. Wenn etwa der E-Mail Service eine Methode isTransmitted() anbietet, so kann der Stub immer true liefern. Oder ein Stub-Repository liefert statt Kunden aus der Datenbank immer die gleichen 10 vorgefertigten Kunden. Oder statt ein langsamer Web-Service-Aufruf die aktuellen Wetterdaten liefert, gibt der Stub vorgefertigte ab. Stubs sind auch praktisch, wenn zum Beispiel eine Gui-Anwendung programmiert wird, die statt echter Datenbankdaten erst einmal mit den Stubs entwickelt wird und so die Demo-Daten anzeigt. Wenn ein Team die Gui baut, ein anderes Team den Service, so k\u00f6nnen beide Gruppen unabh\u00e4ngig arbeiten und das Gui-Team muss nicht erst auf die Implementierung warten.<\/p>\n<p>\u00b7 <em>Mock-Objekte<\/em>. Sie sind noch funktionsreichhaltiger als Stubs und bilden auch komplexe Interaktionen ab. In der Regel werden Mock-Objekte durch eine Bibliothek wie <em>mockito<\/em> (<em>http:\/\/code.google.com\/p\/mockito\/<\/em>) oder <em>EasyMock<\/em> (<em>http:\/\/easymock.org\/<\/em>) \u201eaufgeladen\u201c und zeigen dann das gew\u00fcnschte Verhalten.<\/p>\n<p>Diese drei Typen k\u00f6nnen wir unter dem Oberbegriff <em>Dummy-Objekt<\/em> zusammenfassen. Grunds\u00e4tzlich gilt bei den vier Begriffen aber, dass sie nicht einheitlich von Autoren verwendet werden.<a href=\"#_ftn4_1629\" name=\"_ftnref4_1629\">[4]<\/a><\/p>\n<h4>1.6 JUnit-Erweiterungen, Testzus\u00e4tze<\/h4>\n<p>Das Framework JUnit selbst ist recht kompakt, doch wie es an den Hamcrest-Matchern abzulesen ist, gibt es die Notwendigkeit f\u00fcr komfortable Testmethoden, die h\u00e4ufig wiederkehrende typische Testaufgaben vereinfachen. Dazu z\u00e4hlen nicht nur Methoden, die testen, ob eine Datei vorhanden ist, sondern auch Unterst\u00fctzungen f\u00fcr Tests mit Datenbankzugriffen, Web-Tests oder Gui-Tests.<\/p>\n<h6>Web-Tests<\/h6>\n<p>Beim Testen von Web-Anwendungen kommen zwei Verfahren zum Einsatz. Das eine ist ein werkzeugunterst\u00fctzte Aufzeichnung von Web-Interaktionen und das automatische Abspielen der Folgen f\u00fcr den Test, die andere die programmierte L\u00f6sung. F\u00fcr die Aufzeichnung bietet sich gut das freie <em>Selenium<\/em> (<em>http:\/\/seleniumhq.org\/<\/em>) bzw. die Integration in Firefox mit der <em>Selenium IDE<\/em> (<em>http:\/\/seleniumhq.org\/projects\/ide\/<\/em>) an. Wer Tests programmieren m\u00f6chte, findet mit dem Apache <em>HttpUnit<\/em> (<em>http:\/\/httpunit.sourceforge.net\/<\/em>) und dem <em>LiFT<\/em>-Framwork (<em>https:\/\/lift.dev.java.net\/<\/em>) eine gute Basis.<\/p>\n<h6>Tests der Datenbankschnittstelle<\/h6>\n<p>Der Zugriff zur Datenbank geschieht in der Regel \u00fcber Repository-Klassen (auch DAO-Klassen genannt). Greift ein Service auf eine Datenbank zu, so geht sie immer \u00fcber das Repository. Der Test des Services wird dadurch vereinfacht, dass statt einer Datenbank-Reposity-Implementierung ein Repository-Dummy untergeschoben wird. Bleibt die Frage, wie die Reposity-Klassen zu testen sind.<\/p>\n<p>Tests k\u00f6nnen sehr lange dauern, denn die Interaktion mit der Datenbank ist h\u00e4ufig das langsamste einer ganzen Gesch\u00e4ftsandwendung. Eine Heransgehensweise ist, die Tests lokal im Speicher laufen zu lassen. Dazu werden Im-Memory-Datenbanken wie Derby, H2, HSQLDB verwendet. Die Datenbank ist also rein im Speicher uns so l\u00e4uft ein Test sehr schnell. Der gr\u00f6\u00dfte Nachteil dabei ist aber, dass es SQL-Dialekte gibt und eine In-Memory-Oracle Datenbank gibt es bisher nicht. Wenn die Reposity-Implementierung f\u00fcr Massenoperationen auf eine gespeicherte Oracle-Prozedur zur\u00fcckgreift, so kann das das einfache H2 nicht testen.<\/p>\n<p>Eine weitere Aufgabe ist das F\u00fcllen der Datenbank mit Testdaten. Die Open-Source-Software <em>DbUnit<\/em> (<em>http:\/\/www.dbunit.org\/<\/em>) ist hier eine gro\u00dfe Hilfe. Externe Daten sind in XML verfasst und k\u00f6nne leicht in die Datenbank importiert werden bevor dann der Test auf diesen Probedaten arbeitet. Die Probedaten werden dann, wenn m\u00f6glich, in der In-Memory-Datenbank eingef\u00fcgt oder in einer lokalen Entwicklungsdatenbank. F\u00fcr fortgeschrittne Tests (und insbesondere zum Absch\u00e4tzen der Laufzeit) m\u00fcssen Tests aber auch mit einer Kopie der echten Gesch\u00e4ftsdaten durchgef\u00fchrt werden.<\/p>\n<h6>Ausblick<\/h6>\n<p>Neben JUnit gibt es das Testframework TestNG, welches zum Zeitpunkt von JUnit 3.x die Richtung aufzeigte, mit Annotationen Tests zu deklarieren. Erst sp\u00e4t wechselten die JUnit-Entwickler zu Java 5 und aktualisierten zur Version JUnit 4. In der Zwischenzeit, von JUnit 3.x auf JUnit 4 gab es eine st\u00e4rkere Zuwendung zu TestNG, die aber heutzutage nicht mehr abzulesen ist, da JUnit 4 in letzter Zeit doch wieder aufgeholt hat und interessante M\u00f6glichkeiten bieten. Nicht angesprochen wurden in diesem Kapitel zum Beispiel parametrisierte Tests und Datenpunkte (erwartete Werte kommen automatisch \u00fcber eine Datenstruktur) oder Theorien, wie es das folgende Beispiel kurz zeigt:<\/p>\n<pre>@Theory squareRoot( double n ) {\r\nassumeTrue( n &gt;= 0 );\r\nassertTrue( sqrRoot(n) &gt;= 0 );\r\nassertEquals( n, sqrRoot(n) * sqrRoot(n), 0.01 );\r\n}<\/pre>\n<p>Weiterhin bietet JUnit Rules (Methoden einer mit @Rule annotiere Objektvariablen werden vor, nach oder statt eines Tests aufgerufen) oder auch Scheduling-Strategien mit parallelen Tests als Computer-Modell.<\/p>\n<hr size=\"1\" \/>\n<p><a href=\"#_ftnref1_1629\" name=\"_ftn1_1629\">[1]<\/a> <em>http:\/\/www.time4tea.net\/wiki\/display\/MAIN\/Testing+Files+with+Hamcrest<\/em><\/p>\n<p><a href=\"#_ftnref2_1629\" name=\"_ftn2_1629\">[2]<\/a> Wobei allOf(exists(), sized(0)) noch etwas besser ist \u2013 der Autor ist informiert<\/p>\n<p><a href=\"#_ftnref3_1629\" name=\"_ftn3_1629\">[3]<\/a> Mehr Informationen zum Dateiformat gibt <em>http:\/\/de.wikipedia.org\/wiki\/VCard<\/em>.<\/p>\n<p><a href=\"#_ftnref4_1629\" name=\"_ftn4_1629\">[4]<\/a> Die Seite <em>http:\/\/xunitpatterns.com\/Mocks,%20Fakes,%20Stubs%20and%20Dummies.html<\/em> stellt einige Autoren mit ihrer Begriffsnutzung vor.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hinweis! Eine aktuelle Version des Abschnittes gibt es im Buch &#8222;Java ist auch eine Insel. Einf\u00fchrung, Ausbildung, Praxis&#8220;, dem 1. Inselbuch. (War f\u00fcr den 2. Band vorgesehen, musste wegen der F\u00fclle des 2. Buchs kurzerhand in den 1. Band geschoben werden. Sorry f\u00fcr Irritationen!) 1.1 Softwaretests Um m\u00f6glichst viel Vertrauen in die eigene Codebasis zu [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":"","_links_to":"","_links_to_target":""},"categories":[11],"tags":[],"class_list":["post-560","post","type-post","status-publish","format-standard","hentry","category-insel"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/560","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/comments?post=560"}],"version-history":[{"count":12,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/560\/revisions"}],"predecessor-version":[{"id":3037,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/560\/revisions\/3037"}],"wp:attachment":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/media?parent=560"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/categories?post=560"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/tags?post=560"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}