Einführung in JUnit 4 - ux-02.ha.bib.deux-02.ha.bib.de/daten/Löwe/SWT/Skript++/OO1/Einfuehrung in...

19
Einführung in JUnit 4, Version 1.2 Drachau/Schulz Einführung in JUnit 4 – für LOMF-Programmierer – Version: 1.2 Fragen, Anregungen und Korrekturen zu diesem Dokument bitte per E-Mail an: [email protected] Inhaltsverzeichnis 1 Einleitung.......................................................................................................................................... 2 2 Ein kleiner Crash-Kurs...................................................................................................................... 2 2.1 Aufgabe...................................................................................................................................... 3 2.2 Schnittstelle formulieren............................................................................................................3 2.3 Testrahmen (Testcase) entwickeln............................................................................................. 4 2.4 Testfälle schreiben..................................................................................................................... 5 2.5 Testfälle ausführen..................................................................................................................... 7 3 Mehr über Testfälle............................................................................................................................8 3.1 Trennen von Initialisierung und Auswertung.............................................................................8 3.2 Testfälle und Ausnahmen......................................................................................................... 10 3.2.1 Unerwünschte Ausnahmen...............................................................................................11 3.2.2 Erwünschte Ausnahmen................................................................................................... 11 4 Zusammenfassung........................................................................................................................... 12 Anhang A: Erstellung eines Java-Projekts mit JUnit-Unterstützung................................................. 13 Anhang B: Versionshistorie................................................................................................................ 19 Literaturverzeichnis............................................................................................................................ 19 Abbildungsverzeichnis Abbildung 1: Erstellen eines Testcases.................................................................................................4 Abbildung 2: JUnit-Fenster mit fehlgeschlagenen Tests...................................................................... 7 Abbildung 3: JUnit-Fenster mit erfolgreichen Tests............................................................................ 8 Abbildung 4: Anlegen eines Java-Projekts, Teil 1..............................................................................13 Abbildung 5: Anlegen eines Java-Projekts, Teil 2..............................................................................14 Abbildung 6: Integration der JUnit-Unterstützung: "Libraries" auswählen....................................... 15 Abbildung 7: Integration der JUnit-Unterstützung: "Add Library" auswählen................................. 16 Abbildung 8: Integration der JUnit-Unterstützung: JUnit-Bibliothek auswählen..............................16 Abbildung 9: Integration der JUnit-Unterstützung: JUnit-Version auswählen.................................. 17 Abbildung 10: Abschluss der JUnit-Integration, Teil 1...................................................................... 17 Abbildung 11: Abschluss der JUnit-Integration, Teil 2...................................................................... 18 Abbildung 12: Neues Java-Projekt mit JUnit-Unterstützung............................................................. 18 Tabellenverzeichnis Tabelle 1: Von JUnit angebotene Methoden zum Vergleichen von Soll- und Ist-Werten.....................5 1

Transcript of Einführung in JUnit 4 - ux-02.ha.bib.deux-02.ha.bib.de/daten/Löwe/SWT/Skript++/OO1/Einfuehrung in...

Einführung in JUnit 4, Version 1.2 Drachau/Schulz

Einführung in JUnit 4– für LOMF-Programmierer –

Version: 1.2

Fragen, Anregungen und Korrekturen zu diesem Dokument bitte per E-Mail an:[email protected]

Inhaltsverzeichnis1 Einleitung..........................................................................................................................................22 Ein kleiner Crash-Kurs......................................................................................................................2

2.1 Aufgabe......................................................................................................................................32.2 Schnittstelle formulieren............................................................................................................32.3 Testrahmen (Testcase) entwickeln.............................................................................................42.4 Testfälle schreiben.....................................................................................................................52.5 Testfälle ausführen.....................................................................................................................7

3 Mehr über Testfälle............................................................................................................................83.1 Trennen von Initialisierung und Auswertung.............................................................................83.2 Testfälle und Ausnahmen.........................................................................................................10

3.2.1 Unerwünschte Ausnahmen...............................................................................................113.2.2 Erwünschte Ausnahmen...................................................................................................11

4 Zusammenfassung...........................................................................................................................12Anhang A: Erstellung eines Java-Projekts mit JUnit-Unterstützung.................................................13Anhang B: Versionshistorie................................................................................................................19Literaturverzeichnis............................................................................................................................19

AbbildungsverzeichnisAbbildung 1: Erstellen eines Testcases.................................................................................................4Abbildung 2: JUnit-Fenster mit fehlgeschlagenen Tests......................................................................7Abbildung 3: JUnit-Fenster mit erfolgreichen Tests............................................................................8Abbildung 4: Anlegen eines Java-Projekts, Teil 1..............................................................................13Abbildung 5: Anlegen eines Java-Projekts, Teil 2..............................................................................14Abbildung 6: Integration der JUnit-Unterstützung: "Libraries" auswählen.......................................15Abbildung 7: Integration der JUnit-Unterstützung: "Add Library" auswählen.................................16Abbildung 8: Integration der JUnit-Unterstützung: JUnit-Bibliothek auswählen..............................16Abbildung 9: Integration der JUnit-Unterstützung: JUnit-Version auswählen..................................17Abbildung 10: Abschluss der JUnit-Integration, Teil 1......................................................................17Abbildung 11: Abschluss der JUnit-Integration, Teil 2......................................................................18Abbildung 12: Neues Java-Projekt mit JUnit-Unterstützung.............................................................18

TabellenverzeichnisTabelle 1: Von JUnit angebotene Methoden zum Vergleichen von Soll- und Ist-Werten.....................5

1

Einführung in JUnit 4, Version 1.2 Drachau/Schulz

1 EinleitungHerzlich willkommen in der Welt von Java! Als erfahrener LOMF1-Recke hast du bereits die Höhen und Tiefen der funktionalen Programmierung gemeistert und die Grundlagen der Software-Ent-wicklung verinnerlicht. Begriffe wie Algorithmus, Funktion, Argument, Parameter oder Spezialisie-ren sind für dich keine Fremdwörter mehr. Und auch die allerersten Grundlagen des objektorientier-ten Paradigmas sind dir bereits vermittelt worden.

Umso verständlicher, dass du dich eben nun fragst, wie in Java Programme effektiv und effizient getestet werden können. Wie? Das weißt du bereits? Dann kannst du getrost den Rest des Doku-ments überspringen und dich wichtigeren Aufgaben widmen. Allerdings lernt man bekanntlich nie aus, und vielleicht enthält dieses Dokument doch ein oder zwei praktische Tipps, die dir bei deiner täglichen Arbeit in Java helfen können...

Als LOMF-Programmierer war dir das Testen im Rahmen der Software-Entwicklung sicherlich in Fleisch und Blut übergangen. “Kein Code ohne Testfälle” war die fast schon allgegenwärtige Regel, die dich vor unliebsamen Überraschungen bewahren sollte. Kein Wunder, möchte man meinen, schließlich ist LOMF mit voller Absicht so entwickelt worden, das Testen steckt LOMF sozusagen “im Blut”.

In Java sieht die Sache etwas anders aus. Hier ist das Testen nicht dermaßen in der Sprache veran-kert, wie es bei LOMF der Fall ist. Doch flexible Alternativen und mächtige Werkzeuge existieren auch hier. Insbesondere eine weit verbreitete Bibliothek wird uns in den nächsten Abschnitten be-sonders beschäftigen. Die Rede ist von JUnit2, einer Sammlung von Klassen, die sich einfaches Tes-ten von Software auf die Fahne geschrieben haben. Entworfen und implementiert wurde diese Bi-bliothek von Erich Gamma und Kent Beck, zwei Persönlichkeiten, die einem (angehenden) Pro-grammierer heutzutage zumindest nicht unbekannt sein sollten.3

Wir starten also mit JUnit als Test-Framework4, und – weil das Arbeiten mit einem “gewöhnlichen” Text-Editor meist nicht besonders produktiv ist5 – mit der Java-Entwicklungsumgebung eclipse6. Diese Entwicklungsumgebung hat den großen Vorteil, dass sie JUnit bereits “kennt” und keine künstlichen “Klimmzüge” gemacht werden müssen, um seine Software schnell und einfach testen zu können. Wir erinnern uns: Wenn das Testen nicht schnell und einfach ist, wird es nicht getan. Und das willst du am allerwenigsten, denn schließlich liest du ja ein Dokument übers Testen, oder?

Hinweis: Im Folgenden wird vorausgesetzt, dass du mit den grundlegenden Funktionen von eclipse vertraut bist und weißt, wie man in eclipse ein Java-Projekt anlegt, Quelltext-Dateien bearbeitet und Java-Programme zum Ausführen bringt. Dies ist keine Einführung in eclipse, sondern in JUnit! Falls du dir nicht sicher bist, schau bitte im Anhang A nach, wie du ein Java-Projekt mit JUnit-Unterstützung anlegen kannst.

2 Ein kleiner Crash-KursDieses Kapitel stellt die grundsätzliche Funktionalität von JUnit dar, ohne dass du dich gleich in un-wesentlichen Details verlierst. Es wird ein kleines LOMF-Programm vorgestellt (natürlich mitsamt

1 Less Overhead More Fun, entwickelt von Prof. Dr. Michael Löwe2 s. http://junit.org/3 Was, du kennst sie noch nicht? Dann mach deine Hausaufgaben und lies [GHJV95] und [Beck03], zwei Klassiker

der Computer-Literatur!4 Ein Framework ist eine besondere Art Bibliothek, die so konzipiert ist, dass nicht dein Programm die Bibliothek

ruft, sondern andersherum: Das Framework ruft dein Programm, wenn es notwendig wird. Der Kontrollfluss ist also umgedreht. Dies ist auch bekannt als sog. Hollywood-Prinzip (“Don't call us, we call you”).

5 zumindest nicht für Anfänger!6 s. http://www.eclipse.org/

2

Einführung in JUnit 4, Version 1.2 Drachau/Schulz

Testfällen!), das in ein funktional äquivalentes Java-Programm umgewandelt werden soll. Dabei spielen in unserer Betrachtung die Testfälle eine besonders wichtige Rolle. Du wirst in diesem Ab-schnitt lernen, wie man Testfälle in Java/JUnit7 formuliert und durchführt. Du wirst auch sehen, wie die Testfälle eines LOMF-Programms sich direkt in die Welt von Java/JUnit übertragen lassen. Und los geht's...

2.1 Aufgabe

Gegeben ist das folgende kleine LOMF-Programm zur rekursiven Berechnung der Fakultät:

fac (n : Card) : Card[*Effects :

Computes n! == fac (n).Notes :

n! is recursively defined as:0! == 1n! == n * ((n - 1)!) [n > 0]

*]::=n.= (0).? (

1, /* 0! == 1 (bottom case) */n.* (n.- (1).fac ()) /* recursive invocation */

)

Zu dem Programm existieren folgende LOMF-Testfälle:

fac (0) == 1fac (1) == 1fac (2) == 2fac (3) == 6fac (4) == 24fac (10) == 3628800

Die Aufgabe ist nun, die Funktion fac() in die Programmiersprache Java zu übertragen. Da du ja dir vermutlich vorstellen kannst, dass bei einer solchen Konvertierung einiges schief gehen kann, willst du natürlich erst die Testfälle in Java schreiben. Wie das geht, beschreibt der nächste Ab-schnitt.

Zuerst aber legst du in eclipse ein passendes und vorerst leeres Java-Projekt an (siehe Anhang A:Erstellung eines Java-Projekts mit JUnit-Unterstützung). Der Name “factorial” bietet sich eventuell an.

2.2 Schnittstelle formulieren

Bevor du die Testfälle übernehmen (genauer: konvertieren) kannst, musst du dir über die Schnitt-stelle8 der zu testenden Einheit Gedanken machen. Dies unterscheidet sich nicht von der Entwick-lung in LOMF, wo es hieß: Zuerst Signatur (ohne Implementierung!), dann Testfälle, dann Imple-mentierung. Du musst nun also die Schnittstelle festlegen, über welche die zu übertragende Funkti-on (sprich: fac()) angesprochen werden kann. Für das weitere Vorgehen gehen wir von folgender Klassendefinition aus (die du über “File → New → Class” generieren kannst):

package factorial;

7 eigentlich “in Java in Verbindung mit JUnit”, aber das jedesmal hinzuschreiben ist einfach zu mühselig...8 Achtung: Hier ist “Schnittstelle” in der allgemeinen Bedeutung als “Spezifikation” gemeint (Eingabe, Ausgabe, Ver-

halten, Vor- und Nachbedingungen) und nicht in der speziellen Bedeutung als “Schnittstellen-Klasse”!

3

Einführung in JUnit 4, Version 1.2 Drachau/Schulz

public class Factorial {public int compute (int number) {

return 0;}

}

Diese Definition ist offensichtlich noch nicht vollständig. Sie ist aber syntaktisch einwandfrei, und genau darum geht es im ersten Schritt. Die Funktionalität wirst du erst implementieren, nachdem du die Testfälle geschrieben haben wirst.

2.3 Testrahmen (Testcase) entwickeln

Nachdem du das Problem der Schnittstelle gelöst hast, machst du dich nun endlich daran, die Test-fälle zu implementieren und für JUnit geeignet zu “verpacken”. Diese “Verpackung” wird Testrah-men bzw. Testcase genannt. Dazu legst du zuerst eine neue Klasse mit dem Namen FactorialTest an, welche die Testfälle beinhalten wird. Diese Klasse erbt von TestCase, einer Klasse des JUnit-Frameworks. Warum dies so sein muss, wird später deutlich. Halten wir vorerst fest, dass durch die enge Verwandtschaft das JUnit-Framework später die Tests lokalisieren und ausführen kann. Die Klasse dient dir nun als Testrahmen für die noch zu schreibenden Testfälle.

In eclipse kannst du nun nun zum ersten Mal von der Integration der JUnit-Bibliothek Gebrauch machen: Dazu rufst du nun über “File → New → JUnit Test Case” einen Dialog auf (1), in dem du die Standard-Vorgaben einfach übernimmst und auf “Fertig stellen” drückst. Nun generiert das inte-grierte JUnit-Plugin die entsprechende Klasse automatisch samt notwendiger Oberklasse und import-Klausel.

Du solltest jetzt folgenden Quelltext vor dir sehen:

4

Einführung in JUnit 4, Version 1.2 Drachau/Schulz

package factorial;

import static org.junit.Assert.*;

import org.junit.Test;

public class FactorialTest {

@Testpublic void test() {

fail("Not yet implemented");}

}

Die import-Direktiven sind wichtig, um die benötigten JUnit-Funktionen ohne zusätzliche Na-mensraum-Qualifikation nutzen zu können. Die Methode test wird als Beispiel automatisch gene-riert, wir brauchen sie jedoch nicht, weil wir unsere eigenen Testfälle entwickeln wollen, was im nächsten Abschnitt beschrieben wird. Somit lösche bitte die Methode test, bevor du zum nächsten Abschnitt übergehst.

2.4 Testfälle schreiben

Du hast nun einen funktionierenden Testrahmen (na ja, noch funktioniert anscheinend gar nichts, aber das wird sich bald ändern!) Nun schreibst du die Testfälle, indem du jedem LOMF-Testfall eine eigene Test-Methode spendierst. Dabei musst du folgende Regeln einhalten:

1) Die Methode muss die Annotation @Test besitzen.

2) Der Rumpf enthält einen Aufruf einer der verfügbaren assert-Operationen oder den Aufruf der fail-Operation.

Punkt 1 ist notwendig, damit das JUnit-Framework deine Tests finden und ausführen kann. Punkt 2 ist wichtig, damit das JUnit-Framework überhaupt mitbekommt, wenn irgendwelche getesteten Be-dingungen nicht in Ordnung sind. Die wichtigsten assert-Methoden sind:

assert-Methode FunktionassertTrue (condition) testet, ob condition wahr ist

assertFalse (condition) testet, ob condition falsch ist

assertEquals (expected, actual) testet, ob entweder expected und actual beide null sind oder expected.equals(actual) wahr ist

assertNull (object) testet, ob object == null wahr ist

assertNotNull (object) testet, ob object != null wahr ist

assertSame (expected, actual) testet, ob expected == actual wahr ist

Tabelle 1: Von JUnit angebotene Methoden zum Vergleichen von Soll- und Ist-Werten

In unserem Beispiel prüfen wir ausschließlich auf Gleichheit zweier Zahlen, also ist assertEquals die beste Wahl. Warum, wirst du dich fragen? Schließlich haben primitive Datentypen wie int kei-ne Operation equals! Das stimmt, aber assertEquals ist für alle möglichen primitiven Datenty-pen, einschließlich int, überladen. Dabei werden die primitiven Datentypen in die entsprechenden Wrapper-Objekte gepackt (z.B. bei int in Integer-Objekte). Diese werden anschließend per equals verglichen. Deshalb ist es immer angebracht, assertEquals zu benutzen, wenn es um den

5

Einführung in JUnit 4, Version 1.2 Drachau/Schulz

Vergleich auf Äquivalenz geht. assertSame solltest du nur verwenden, wenn du wirklich Objekt-Identität prüfen möchtest, und das ist selten notwendig.

Auch solltest du die assert-Methoden, die von JUnit angeboten werden, nicht mit dem assert-Schlüsselwort verwechseln, dass ab der Version 1.4 des JDKs9 existiert. Die JUnit-Methoden sind völlig normale (statische) Methoden, die in der Klasse org.junit.Assert definiert sind. Dazu aber später mehr.

Genug geredet, nun wird kodiert (der unveränderte Quelltext wird grau dargestellt):

package factorial;

import static org.junit.Assert.*;

import org.junit.Test;

public class FactorialTest {@Testpublic void fac0 () {

assertEquals (1, new Factorial ().compute (0));}@Testpublic void fac1 () {

assertEquals (1, new Factorial ().compute (1));}@Testpublic void fac2 () {

assertEquals (2, new Factorial ().compute (2));}@Testpublic void fac3 () {

assertEquals (6, new Factorial ().compute (3));}@Testpublic void fac4 () {

assertEquals (24, new Factorial ().compute (4));}@Testpublic void fac10 () {

assertEquals (3628800, new Factorial ().compute (10));}

}

Wie du siehst, existiert nun für jede Zeile im LOMF-Test eine korrespondierende Test-Methode. Vielleicht wundert dich, dass die assertEquals-Argumente gegenüber den LOMF-Vergleichen in der Reihenfolge vertauscht sind. Dies ist Absicht: Die Signatur von assertEquals erwartet zuerst den Soll-Wert und anschließend den Ist-Wert. Warum ist das so wichtig, wirst du bestimmt denken? Schließlich ist Gleichheit eine Äquivalenzrelation und somit symmetrisch! Das ist richtig, aber es geht hier um die Aussagekraft der Meldungen, wenn ein Testfall fehlschlägt. Wenn jetzt Testfall fac0 ausgeführt wird, schlägt der Vergleich fehl, und JUnit generiert die Fehlermeldung:

java.lang.AssertionError: expected:<1> but was:<0>

Im umgekehrten Fall wird die Fehlermeldung

java.lang.AssertionError: expected:<0> but was:<1>

9 Kurzform für Java Development Kit

6

Einführung in JUnit 4, Version 1.2 Drachau/Schulz

generiert. Und die ist zweifelsohne unsinnig, oder?

2.5 Testfälle ausführen

Nach soviel getaner Arbeit willst du jetzt sicherlich die Früchte deines Tuns ernten. Dazu wählst du die Klasse im Editor aus, welche die auszuführenden Testfälle enthält (in unserem Beispiel also die Klasse FactorialTest), und wählst den Menüpunkt “Run → Run As → JUnit Test”. Die eclipse-Umgebung lädt und startet daraufhin eine JUnit-Klasse, welche die Testfälle aus der markierten Klasse extrahiert und nacheinander ausführt.

Nach der Ausführung erhältst du unter dem Editor neben “Konsole” und “Tasks” eine Lasche “JU-nit”10, die Details zu den ausgeführten Testfällen anzeigt (vgl. 2). Das wichtigste hierbei ist der di-cke Balken im oberen Teil des Fensters. Wenn er grün ist, waren alle Tests erfolgreich. Wenn er hin-gegen rot ist, hat mindestens ein Testfall aus irgendeinem Grund nicht funktioniert!

Es gibt – aus der Sicht von JUnit – zwei Arten von Fehlern, die bei Tests gefunden werden können. Störungen (bzw. Failures) liegen vor, wenn der Test auf Grund eines Aufrufs einer JUnit-Methode fehlschlägt, also z. B. durch den Aufruf einer assert-Methode, wenn die zu testende Bedingung nicht eintrifft. Fehler (bzw. Errors) liegen vor, wenn die Test-Methode durch eine nicht vorhergese-hene Ausnahme beendet wird. Diese beiden Fälle werden gesondert behandelt, und das aus gutem Grund. Denn unbehandelte Ausnahmen werden als “außergewöhnlich” betrachtet, etwas, was nicht vorkommen sollte. Fehlgeschlagene Tests auf Grund von fehlgeschlagenen Vergleichen hingegen sind “vorherzusehen” und während der Entwicklung meistens keine Ausnahme.

Wie du siehst, sind alle Testfälle fehlgeschlagen, der Balken ist rot. Das ist natürlich kein Wunder. Jetzt geht es darum, die Funktion so zu “korrigieren”, dass die Testfälle erfolgreich sind. Nach [Beck03] gibt es dazu mehrere Möglichkeiten11:

1) “Obvious Implementation”: Die offensichtliche Implementierung wird kodiert.

2) “Fake It”: Die Implementierung wird geringfügig modifiziert, dass die Testfälle erfolgreich sind; danach wird refaktorisiert, indem eingeführte Konstanten schrittweise in Variablen umgewandelt werden.

3) “Triangulation”: Aus zwei oder mehr Testfällen wird abstrahiert.

In unserem Fall ist die Implementierung der Fakultät relativ einfach, da wir auch eine funktionsfähi-ge Grundlage besitzen. Wir wählen also Methode 1 und kodieren:

10 Da dies ein verschiebbares Fenster ist, kann es sich gelegentlich auch woanders befinden, etwa als Lasche neben dem Paket-Explorer!

11 Dies ist nur eine sehr, sehr kurze Einführung in die Test-getriebene Entwicklung. Für weitere Details vgl. [Beck03].

7

Abbildung 2: JUnit-Fenster mit fehlgeschlagenen Tests

Einführung in JUnit 4, Version 1.2 Drachau/Schulz

package factorial;

public class Factorial {public int compute (int number) {

if (number == 0)return 1;

elsereturn number * compute (number - 1);

}}

Nun führst du die Tests noch einmal aus, und voilà: der Balken ist grün, die Tests waren erfolgreich (vgl. 3). Unsere Aufgabe ist also geschafft. Oder nicht?

3 Mehr über Testfälle

3.1 Trennen von Initialisierung und Auswertung

Wenn du dir die Testfälle noch einmal genauer anschaust, wirst du feststellen, dass in jedem einzel-nen Testfall ein Factorial-Objekt erzeugt wird, um die Fakultät zu berechnen. Weil der auszufüh-rende Programmcode zur Erzeugung des Objekts in jeder Testmethode derselbe ist, bietet es sich an, die Erzeugung “herauszuziehen” und in eine eigene Methode zu verlagern. Wir nennen diese Me-thode setUp und rufen sie dann in jeder Testmethode auf:

package factorial;

import static org.junit.Assert.*;

import org.junit.Test;

public class FactorialTest {private Factorial m_factorial;public void setUp () {

m_factorial = new Factorial ();}@Testpublic void fac0 () {

setUp ();assertEquals (1, m_factorial.compute (0));

}@Testpublic void fac1 () {

setUp ();

8

Abbildung 3: JUnit-Fenster mit erfolgreichen Tests

Einführung in JUnit 4, Version 1.2 Drachau/Schulz

assertEquals (1, m_factorial.compute (1));}@Testpublic void fac2 () {

setUp ();assertEquals (2, m_factorial.compute (2));

}@Testpublic void fac3 () {

setUp ();assertEquals (6, m_factorial.compute (3));

}@Testpublic void fac4 () {

setUp ();assertEquals (24, m_factorial.compute (4));

}@Testpublic void fac10 () {

setUp ();assertEquals (3628800, m_factorial.compute (10));

}}

Bevor wir irgendetwas weiter an dem Quelltext verändern, müssen wir die Tests laufen lassen. Warum? Bei jeder Änderung können sich Fehler einschleichen! Deshalb haben wir uns die Testfälle gebastelt: damit wir Fehler finden! Auch wenn Testfälle geändert werden, wollen wir sichergehen, dass keine unerwünschten Nebeneffekte (an die wir nicht gedacht haben) auftreten. Deshalb führen wir die Tests aus und fühlen uns gut, weil wir den grünen Balken sehen.

Jetzt wirst du sicherlich einwenden, dass in diesem speziellen Fall durch die Refaktorisierung nicht viel gewonnen ist: Der Quelltext ist wesentlich länger geworden. Dieser Einwand ist berechtigt, wird aber durch den offensichtlichen strukturellen Gewinn kompensiert: Nun ist die Testfall-Initiali-sierung strikt von der Testfall-Auswertung getrennt.

Dies ist ein großer Vorteil, weil JUnit diese Trennung von initialisierenden, auswertenden (und ab-schließenden, s. u.) Operationen “von Haus aus” unterstützt. Wenn wir nämlich der oben eingefüg-ten Methode setUp einfach die Annotation @Before spendieren, können wir uns die ganzen Aufrufe dieser Methode sparen, weil JUnit vor (= „before“!) dem Ausführen jeder einzelnen Testfall-Metho-de diese Methode aufrufen wird – und das völlig automatisch!

Um die Annotation @Before auch nutzen zu können, müssen wir sie jedoch vorher noch importie-ren. Dies dient lediglich dem Zweck, nicht @org.junit.Before schreiben zu müssen.

Wir verändern also den Quelltext wie folgt:

package factorial;

import static org.junit.Assert.*;

import org.junit.Test;import org.junit.Before;

public class FactorialTest {private Factorial m_factorial;@Beforepublic void setUp () {

m_factorial = new Factorial ();

9

Einführung in JUnit 4, Version 1.2 Drachau/Schulz

}@Testpublic void fac0 () {

//setUp ();assertEquals (1, m_factorial.compute (0));

}@Testpublic void fac1 () {

//setUp ();assertEquals (1, m_factorial.compute (1));

}@Testpublic void fac2 () {

//setUp ();assertEquals (2, m_factorial.compute (2));

}@Testpublic void fac3 () {

//setUp ();assertEquals (6, m_factorial.compute (3));

}@Testpublic void fac4 () {

//setUp ();assertEquals (24, m_factorial.compute (4));

}@Testpublic void fac10 () {

//setUp ();assertEquals (3628800, m_factorial.compute (10));

}}

(Die Aufrufe von setUp kannst du natürlich auch löschen; sie sind in diesem Beispiel auskommen-tiert, damit du im Dokument erkennen kannst, dass an dieser Stelle eine Veränderung stattgefunden hat.)

Nun lässt du abermals alle Testfälle durchlaufen und siehst am grünen Balken, dass die durchge-führten Änderungen keinen negativen Einfluss auf die Richtigkeit der Tests haben. Der Test-Code ist jedoch übersichtlicher geworden, da Erzeugung und Benutzung der zu testenden Objekte sicht-bar voneinander getrennt wurden: Die Initialisierung der Testfälle steht in der setUp-Methode, die Ausführung der Testfälle ist in den test...-Methoden zu finden.

3.2 Testfälle und Ausnahmen

Wenn Programmcode getestet werden soll, der in der Lage ist, Ausnahmen auszulösen, ist etwas mehr (aber nicht wirklich viel) Arbeit notwendig. Zuerst musst du zwischen zwei möglichen Situa-tionen unterscheiden:

• Du möchtest Programmcode testen, der eigentlich keine Ausnahme auslösen sollte. Falls eine Ausnahme dennoch auftritt, ist der Testfall fehlgeschlagen.

• Du möchtest Programmcode testen, der eine Ausnahme auslösen soll. Falls keine Ausnahme auf-tritt, ist der Testfall fehlgeschlagen.

10

Einführung in JUnit 4, Version 1.2 Drachau/Schulz

3.2.1 Unerwünschte Ausnahmen

Der erste Fall ist der einfachere. Hier erweiterst du deine Test-Methode einfach um eine passende throws-Klausel. Gesetzt den Fall, wir haben den folgenden Testfall:

import static org.junit.Assert.*;import org.junit.Test;

public class MyClassTest {@Testpublic void testDoSomething () {

assertEquals (42, new MyClass ().doSomething ());}

}

Nehmen wir an, dass doSomething eine Ausnahme vom Typ MyException auslösen könnte (aber nicht sollte), so erweitern wir die Methode um die passende throws-Klausel:

import static org.junit.Assert.*;import org.junit.Test;

public class MyClassTest {@Testpublic void testDoSomething () throws MyException {

assertEquals (42, new MyClass ().doSomething ());}

}

Das war auch schon alles.

3.2.2 Erwünschte Ausnahmen

Gelegentlich kommt es vor, dass du in einem Testfall prüfen möchtest, ob eine bestimmte Ausnah-me ausgeworfen wird. Normalerweise erwartest du, dass keine Ausnahmen während eines Testfalls auftreten (ansonsten kommt es ja bekanntlich zu einem Fehler). Um das Gegenteil zu erreichen, d. h. die Existenz einer Ausnahme als ”richtig” und die Abwesenheit derselben als ”falsch” zu be-handeln, müssen wir etwas mehr tun als gewöhnlich.

Gesetzt den Fall, wir haben den folgenden (noch nicht fertigen) Testfall:

import static org.junit.Assert.*;import org.junit.Test;

public class MyOtherClassTest {@Testpublic void testThrowException () {

new MyOtherClass ().throwException ();}

}

Wir wissen, dass throwException eine Ausnahme vom Typ MyOtherException auswerfen soll, und wir wollen dies überprüfen. Dazu müssen wir die Anweisung oder den Code-Block, der die Ausnahme generiert, in einen try/catch-Block einschließen, um die die Ausnahme aufzufangen:

import static org.junit.Assert.*;import org.junit.Test;

public class MyOtherClassTest {@Test

11

Einführung in JUnit 4, Version 1.2 Drachau/Schulz

public void testThrowException () {try {

new MyOtherClass ().throwException ();}catch (MyOtherException e) {

// alles in Ordnung!}

}}

Nun überlegen wir: Wenn die Ausnahme ausgeworfen wird, geraten wir unweigerlich in den catch-Block. Da in diesem Fall jedoch alles in Ordnung ist (schließlich haben wir die Ausnahme erwartet), bleibt der Anweisungsteil im catch-Block leer. Wenn jedoch keine Ausnahme ausge-worfen wird, beendet sich der Testfall bis jetzt ebenfalls auf “natürlichem” Wege. Das ist schlecht, denn so bekommt JUnit nicht mit, dass eigentlich eine Störung aufgetreten ist (eine erwartete Aus-nahme ist nicht eingetreten). Deshalb ändern wir den obigen Code leicht ab und rufen nach der An-weisung, die eine Ausnahme auswerfen soll, eine JUnit-Operation auf, um einen Fehler zu melden:

import static org.junit.Assert.*;import org.junit.Test;

public class MyOtherClassTest {@Testpublic void testThrowException () {

try {new MyOtherClass ().throwException ();fail ("MyOtherException expected");

}catch (MyOtherException e) {

// alles in Ordnung!}

}}

Die Methode fail, die hier aufgerufen wird, dient dazu, einen Testfall mit einem Fehler zu been-den. Optional kann man (wie im Beispiel) eine zusätzliche Nachricht übergeben, um den ausgelös-ten Fehler genauer zu kennzeichnen.12

4 ZusammenfassungHerzlichen Glückwunsch! Du hast die JUnit-Einführung für LOMF-Programmierer durchgearbeitet und verstanden, wie du die aus LOMF gewohnte Test-getriebene Entwicklung auf Java übertragen kannst. Neben grundlegenden Konzepten wie TestCases und asserts hast du auch gelernt, wie du die Initialisierung von Testfällen auslagern kannst. Auch weißt du jetzt, wie du in deinen Testfällen mit Ausnahmen umgehen kannst. Nun bist du hoffentlich fit für deine nächsten Java-Projekte. Viel Spaß!

12 Es gibt auch noch eine andere Möglichkeit, dieses Problem zu lösen, nämlich mit Hilfe des Parameters „expected“-Parameters der Annotation @Test. Diese Vorgehensweise ist jedoch nicht empfehlenswert, da man keinen Einfluss darauf hat, in welchen Code-Abschnitten der Test-Methode die Ausnahme erwartet wird und in welchen nicht.

12

Einführung in JUnit 4, Version 1.2 Drachau/Schulz

Anhang A: Erstellung eines Java-Projekts mit JUnit-Unterstüt-zungDas Anlegen eines Java-Projekts mit JUnit-Unterstützung läuft prinzipiell genauso ab wie das Anle-gen eines jeden anderen Java-Projekts: Zuerst musst du über das Menü (oder über die Symbolleiste) den entsprechenden Punkt auswählen (4) und dann den Projektnamen vergeben. Anschließend ist es wichtig, nicht die Schaltfläche „Finish“, sondern die Schaltfläche „Next“ auszuwählen (5). An-schließend musst du unter „Libraries“ (6) eine neue „Library“ hinzuzufügen (7), nämlich JUnit (8). Du solltest die Version 4 auswählen (9), weil diese Version in diesem Dokument beschrieben wird. Nach zweimaliger Auswahl der Schaltfläche „Finish“ (10 und 11) ist das Java-Projekt samt JUnit-Unterstützung in eclipse vorhanden (12).

13

Abbildung 4: Anlegen eines Java-Projekts, Teil 1

Einführung in JUnit 4, Version 1.2 Drachau/Schulz

14

Einführung in JUnit 4, Version 1.2 Drachau/Schulz

15

Einführung in JUnit 4, Version 1.2 Drachau/Schulz

16

Einführung in JUnit 4, Version 1.2 Drachau/Schulz

17

Abbildung 9: Integration der JUnit-Unterstützung: JUnit-Version aus-wählen

Einführung in JUnit 4, Version 1.2 Drachau/Schulz

18

Einführung in JUnit 4, Version 1.2 Drachau/Schulz

Anhang B: Versionshistorie

Version Datum Änderung

0.1 08.03.2004 erste Version

0.2 12.03.2004 Anhang A hinzugefügt, optische Verbesserungen

0.3 17.03.2004 kleine Verbesserungen

0.4 16.08.2005 Thema “Ausnahme-Behandlung” hinzugefügt, optische Verbesserungen

0.5 16.08.2005 kleinere Korrekturen

1.0 12.07.2015 Umstellung auf JUnit 4

1.1 12.07.2015 Layout korrigiert, Schriftarten ersetzt

1.2 13.07.2015 Tabellenunterschrift korrigiert, fehlende Test-Annotationen ergänzt

Literaturverzeichnis[GHJV95] Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John: Design Patterns,

1995, Addison-Wesley[Beck03] Beck, Kent: Test-Driven Development, 2003, Addison-Wesley

19