JUnit - FH Wedel

Werbung
JUnit
Eine Testumgebung für Java
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Gliederung
• Was ist JUnit / Motivation
• Allgemeine Struktur von JUnit
• Beispiel / Verfeinerung
• Zusammenfassung
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Was ist JUnit?
•
•
•
•
•
JUnit
JUnit
JUnit
JUnit
JUnit
ist ein Test-Framework.
bietet Klassen an um geschriebenen Quelltext leicht zu prüfen.
verlangt während der Tests keine Benutzerinteraktion.
verlangt ein wenig Disziplin.
ist einfach anzuwenden.
Was ist JUnit nicht?
• Ein Wundermittel – die Tests schreiben sich nicht von selbst.
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Motivation – warum JUnit?
• Tests sind mindestens so wichtig wie Programmierung an sich.
• Tests werden oftmals vernachlässigt:
– Wohlgefühl während des Programmierens.
– Straffer Zeitplan.
• Es gibt nur beschränkte Möglichkeiten um Tests durchzuführen:
– Debuger
• Je umfangreicher das Projekt, desto mühseliger und
komplexer.
– Standardausgabe
• Unübersichtlich in Code und Ausgabe, Beispiel: for-Schleife.
• JUnit schafft Abhilfe.
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Grundprinzipien!
• Erst denken, dann coden.
• Erst testen (!) dann coden.
• Test a little, write a little, test a little, write a little.
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Struktur von JUnit
• Installation: einfach die junit.jar dem CLASSPATH hinzufügen.
• Alle zum Testen notwendigen Klassen sind im Paket junit.framework
enthalten.
• Weiteres Prinzip: zur jeder verfassten Klasse eine Testklasse
entwerfen.
• Zum Schreiben von Tests werden lediglich benötigt: TestCase,
Assert, (TestSuite)
• Zum Ausführen der Tests werden benötigt: TestRunner
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
TestCase (1)
• TestCase stellt bei der Implementierung eigener Tests die
abzuleitende Klasse dar.
• Das Interface Test soll vorerst vernachlässigt werden.
public abstract class TestCase implements Test {
private final String fName;
public TestCase(String name) { fName = name };
public void run() {
setUp();
runTest();
tearDown();
}
}
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
TestCase (2)
• setUp(): Bereitet die Umgebung darauf vor den Test durchzuführen
(Initialisierung von Fixtures).
• runTest(): führt den eigentlichen Test aus.
• tearDown(): Kann dazu benutzt werden die mit setUp()
initialisierten Werte aufzuräumen (z.B. Netzwerkverbindung kappen).
• Template:
protected void setUp() { };
protected void runTest() { };
protected void tearDown() { };
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
TestCase (3)
• Protokollierung der Fehler durch TestResult.
public abstract class TestCase implements Test {
// ...
public void run(TestResult result) {
result.startTest(this);
setUp();
runTest();
tearDown();
}
public TestResult run() {
TestResult result = new TestResult();
run(result);
return result;
}
}
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Grundsätzlicher Testablauf (1)
• Jeder Test wird grundsätzlich gekapselt:
– Vor jedem Test werden die Werte mit setUp() initialisiert,
– mit runTest() der Test durchgeführt und
– mit tearDown() aufgeräumt.
• Jeder Aufruf der drei Routinen ist durch try-catch-Blöcke gekapselt:
– In jeden Fall werden alle Tests durchgeführt.
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Grundsätzlicher Testablauf (2)
new XXXTest(“testFoo“);
setUp();
runTest();
tearDown();
new XXXTest(“testFoo“);
setUp();
runTest();
tearDown();
new XXXTest(“testFoo“);
setUp();
runTest();
tearDown();
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
TestResult (1)
• Die Klasse TestResult zählt die durchgeführten Tests.
public class TestResult extends Object {
protected int fRunTests;
public TestResult() {
fRunTests = 0;
}
public synchronized void startTest(Test test) {
fRunTests++;
}
}
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
TestResult (2)
• Weiterhin werden hier die Fehler gespeichert.
public class TestResult extends Object {
// ...
protected Vector fFailures;
protected Vector fErrors;
public synchronized void addError(Test test, Throwable t) {
fErrors.addElement(new TestFailure(test, t));
}
public synchronized void addFailure(Test test, Throwable t) {
fFailures.addElement(new TestFailure(test, t));
}
}
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Fehler oder Fehler?
• JUnit unterscheidet zwei Arten von Fehlern:
– failures: Fehler die durch die negative Auswertung einer zuvor
gestellten Behauptung entstanden sind.
– errors: Fehler die unerwartet entstanden sind, wie z.B. eine
ArrayIndexOutOfBoundException
• Die Klasse TestFailure dient nur zur Speicherung der Fehler im
Vector.
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
TestCase (4)
public void run(TestResult result) {
result.startTest(this);
setUp();
try {
runTest();
} catch (AssertionFailedError e1) {
result.addFailure(this, e1);
} catch (Throwable e2) {
result.addError(this, e2);
} finally {
tearDown();
}
}
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
TestCase, Assert und TestRunner
• JUnit stellt die abstrakte Klasse TestCase zur Verfügung.
• TestCase wird auf einen speziellen Testfall abgeleitet und erweitert.
• Framework führt die abgeleiteten Testfälle aus.
• Assert: eine Behauptung.
• Wir behaupten, dass das Ergebnis eines Tests ein bestimmtes
Ergebnis zurückliefern sollte.
• Der TestRunner führt den eigentlichen Test des Codes durch.
– Text basierend
– Swing basierend
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Assert
• Assert = Behauptungen.
– True: Wahrheit
– False: Falschheit
– Null: Wert gleich Null.
– NotNull: Wert nicht gleich Null.
– Same: Referenz stimmt überein.
– NotSame: Referenz stimmt nicht überein.
– Equals: Ruft Object.equals auf.
• Werfen AssertionFailedError wenn Test fehlschlägt.
Assert.assertTrue(expected.equals(result));
Assert.assertEquals(foo, coo);
Assert.assertEquals(“foo coo test“, foo, coo);
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Ein Beispiel – Klasse Money
• Klasse zum Speichern von Geldbeträgen.
• Unterstützung von verschiedenen Währungen.
– Vorerst: nur eine Währung.
• Addition von Währungen soll ermöglicht werden.
Es gilt:
• Erst testen, dann coden!
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Simpler Testfall
• Zwei Beträge: 12 und 14 Euro.
• Ergebnis der Addition ist richtig wenn 12 + 14 = 26 Euro.
public class MoneyTest extends TestCase {
public void testSimpleAdd() {
Money m12EUR = new Money(12, "EUR");
Money m14EUR = new Money(14, "EUR");
Money expected = new Money(26, "EUR");
Money result = m12EUR.add(m14EUR);
Assert.assertTrue(expected.equals(result));
}
}
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Identifikation
• Um einen bestimmten Testfall später leichter identifizieren zu
können, wird ein Name vergeben.
• Der TestRunner wird diesen später anzeigen.
public class MoneyTest extends TestCase {
// ...
public MoneyTest(String name) {
super(name);
}
}
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
MoneyTest – Stand 1
import junit.framework.*;
public class MoneyTest extends TestCase {
public MoneyTest(String name) {
super(name);
}
public void testSimpleAdd() {
Money m12EUR = new Money(12, "EUR");
Money m14EUR = new Money(14, "EUR");
Money expected = new Money(26, "EUR");
Money result = m12EUR.add(m14EUR);
Assert.assertTrue(expected.equals(result));
}
}
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Money
• Ausgehend vom Test können wir nun die Klasse konstruieren.
class Money {
private double amount;
private String currency;
public Money(double amount, String currency) {
this.amount = amount;
this.currency = currency;
}
public double getAmount() { return this.amount; }
public String getCurrency() { return this.currency; }
}
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Money - Addition
• Hinzufügen der Addition.
– Vorerst nur Addition von gleichen Währungen.
public Money add(Money oMoney) {
return new Money(this.amount + oMoney.getAmount(), this.currency);
}
Was ist mit der Gleichheit?
• Ist gewährleistet wenn Betrag und Währung übereinstimmen, nicht
die Referenz, also:
– Object.equals muss überschrieben werden, jedoch:
• Zuerst die Anforderung an Money.equals festlegen.
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Gleichheit - Anforderungen
•
•
•
•
•
Darf nicht null gleichen.
Muss sich selbst gleichen.
Muss neuem Objekt mit gleichem Betrag und Währung gleichen.
Darf nicht anderem Betrag/Währung gleichen.
etc.
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Gleichheit - Test
• Der Test auf Gleichheit ergibt sich zu:
public void testEquals() {
Money m12EUR = new Money(12, "EUR");
Money m14EUR = new Money(14, "EUR");
Assert.assertTrue(!m12EUR.equals(null));
Assert.assertEquals(m12EUR, m12EUR);
Assert.assertEquals(m12EUR, new Money(12, "EUR"));
Assert.assertTrue(!m12EUR.equals(m14EUR));
}
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Addition und Gleichheit: Codeverdopplung
• Codeverdopplung schon in diesen kleinen Tests:
Money m12EUR = new Money(12, "EUR");
Money m14EUR = new Money(14, "EUR");
• Abhilfe: Fixtures (Inventar bzw. Ausstattung).
• Fixtures definieren Objekte die in mehreren Tests verwendet werden
können.
• Fixtures werden durch Überschreiben der abstrakte Methode
TestCase.setUp() initialisiert.
– Initialisierung findet vor jedem einzelnen Testlauf statt.
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Fixture (MoneyTest – Stand 2)
import junit.framework.*;
public class MoneyTest extends TestCase {
private Money f12EUR;
private Money f14EUR;
protected void setUp() {
this.f12EUR = new Money(12, "EUR");
this.f14EUR = new Money(14, "EUR");
}
public void testSimpleAdd() {
Money expected = new Money(26, "EUR");
Money result = this.f12EUR.add(this.f14EUR);
Assert.assertTrue(expected.equals(result));
}
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Fixture (MoneyTest – Stand 2)
• Und der Test auf Gleichheit:
public void testEquals() {
Assert.assertTrue(!this.f12EUR.equals(null));
Assert.assertEquals(this.f12EUR, this.f12EUR);
Assert.assertEquals(this.f12EUR, new Money(12, "EUR"));
Assert.assertTrue(!this.f12EUR.equals(this.f14EUR));
}
}
Und nun: Money.equals
• Basierend auf dem Test kann nun Money mit equals erweitert
werden.
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Money.equals
public boolean equals(Object aObject) {
if(aObject instanceof Money) {
Money aMoney = (Money) aObject;
return aMoney.getCurrency().equals(this.currency) &&
aMoney.getAmount() == this.amount;
}
return false;
}
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Ein erster Test
• Nachdem wir beide, MoneyTest und Money erstellt haben, kann nun
ein erster Test erfolgen.
• Die Testumgebung kann dabei
– als Swing-Anwendung bzw.
– Textbasierend ablaufen.
public class MoneyTest extends TestCase {
// ...
public static void main(String[] args) {
junit.swingui.TestRunner.run(MoneyTest.class);
//junit.textui.TestRunner.run(MoneyTest.class);
}
}
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Der Test – was passiert?
•
junit.swingui.TestRunner.run(MoneyTest.class)?
• Dies ist die einfachste Art des Testens.
• TestRunner.runTest() durchsucht die Testklasse nach Methoden
die:
– Mit der Zeichenkette „test“ anfangen und
– keine Parameter entgegennehmen sowie
– keinen Rückgabewert haben (void).
• Die Rheinfolge der Aufrufe ist grundsätzlich undefiniert.
• Andere Möglichkeiten? Ja:
– Individuelle Tests
– Testsuiten (test suite)
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Individuelle Tests
• Dienen dazu spezielle Tests, also „nicht alle“ Tests die in einer
Testklasse deklariert sind aufzurufen (Selektion).
• Zwei Arten von individuellen Tests:
– statisch
– dynamisch
• Statisch: Typsicher, jedoch länger.
• Dynamisch: Kurz, jedoch nicht typsicher.
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Individuelle Tests - statisch
• Statisch bedeutet: überschreiben der abstrakten TestCase.runTest
Methode durch eine anonyme innere Klasse:
TestCase test = new MoneyTest("simple add") {
public void runTest() {
testSimpleAdd();
}
};
• Wie schon besprochen: unübersichtlich und lang, jedoch typsicher.
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Individuelle Tests - dynamisch
• Der TestRunner findet die entsprechende Testmethode während der
Laufzeit.
• Deklaration:
TestCase test = new MoneyTest("testSimpleAdd");
• Dabei wird die Testklasse nach der Methode testSimpleAdd
durchsucht und falls gefunden diese aufgerufen.
• NoSuchMethodException ist möglich (Runtime)!
– z.B. bei Tippfehlern
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
TestCase (5)
protected void runTest() throws Throwable {
Method runMethod = null;
try {
runMethod = getClass().getMethod(fName, new Class[0]);
} catch (NoSuchMethodException e) {
assert("Method \""+fName+"\" not found", false);
}
try {
runMethod.invoke(this, new Class[0]);
}
// catch InvocationTargetException and IllegalAccessException
}
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Testsuiten (test suite), Klasse TestSuite (1)
• Dienen dazu verschiedene Tests in einer bestimmten Rheinfolge
aufzurufen.
• Suiten werden durch die Deklaration der Methode suite durch den
TestRunner identifiziert.
public class MoneyTest extends TestCase {
// ...
public static Test suite() {
TestSuite suite = new TestSuite();
suite.addTest(new MoneyTest("testEquals")); // dynamisch!
suite.addTest(new MoneyTest("testSimpleAdd")); // dynamisch!
return suite;
}
}
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Testsuiten (2)
• Suiten können jedoch auch dazu verwendet werden, verschiedene
Klassen eines Paketes bzw. Projektes auf einmal zu testen.
• Die main-Methode wird dabei in eine neue Klasse verschoben welche
– die Tests der einzelnen Klassen übernimmt.
• Hierzu eignet sich z.B. folgendes Konstrukt (GirlTest und BoyTest
werden vorausgesetzt):
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Testsuiten (3)
import junit.framework.*;
public class AllTests {
public static Test suite() {
TestSuite suite = new TestSuite();
suite.addTestSuite(BoyTest.class);
suite.addTestSuite(GirlTest.class);
suite.addTestSuite(MoneyTest.class);
return suite;
}
public static void main(String[] args) {
junit.swingui.TestRunner.run(AllTests.class);
}
}
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Übersicht TestCase und TestSuite
• Durch das gemeinsame Interface Test ist es möglich Tests von
Suiten von Suiten von Cases zu tätigen.
– Vereinfachung: Mit einem einzelnem Aufruf Test von ganzen
Paketen möglich.
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Übersicht Testablauf - TestRunner
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Entwicklung und Tests
• Wann sollte getestet werden?
– Möglichst gleich nach dem Kompilieren.
– Auf diese Weise erfahren wir schnell wann der Code anfängt zu
funktionieren und wann er damit aufhört.
– Hierzu kann z.B. der textbasierende TestRunner benutzt werden.
• Wie soll man entwickeln?
– Ein wenig testen, ein wenig entwickeln.
– Hierzu: Die Anforderungen an den zu schreibenden Code sind nie
mehr so genau bewusst wie während des Niederschreibens.
– Deshalb: Test schreiben, Code schreiben, compilieren und gleich
Testen.
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Weitere Entwicklung (1)
• Ausgehend vom bisherigen Stand von Money Erweiterung auf mehrere
Währungen.
• Bei Addition Wechsel auf die erste übergebene Währung.
• Menge an Geld nur tagesgenau wichtig (Speicherung in Money).
• Wir erweitern den Test wie folgt:
public void testChange() {
Assert.assertEquals(this.f12EUR.add(this.f10USD),
this.f12EUR.add(new Money(10 *
Bank.getTodaysRate("USD","EUR"),"EUR")));
}
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Weitere Entwicklung (2)
• Erweiterung Fixture:
public class MoneyTest extends TestCase {
// ...
private Money f10USD;
protected void setUp() {
// ...
this.f10USD = new Money(10, "USD");
}
}
• Ergibt Fehler, da Money.add zur Zeit nur eine Währung unterstützt.
– Folglich: Nun Erweiterung von Money.
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Weitere Entwicklung (3)
• Ausgehend vom negativen Testergebnis nun die Korrektur von Money.
public Money add(Money oMoney) {
if(this.currency.equals(oMoney.getCurrency())) {
return new Money(this.amount + oMoney.getAmount(), this.currency);
} else {
return new Money(this.amount + oMoney.getAmount() *
Bank.getTodaysRate(oMoney.getCurrency(), this.currency),
this.currency);
}
}
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Testen von Exceptions (1)
• Manchmal möchte man sich vergewissern, dass eine bestimmte
Exception geworfen wird.
• Annahme: Es gibt keine negativen Geldbeträge. Wenn also ein
Money(-12, “EUR“) erstellt wird, soll eine Exception geworfen
werden.
• Problem: Eine Exception wird JUnit als einen Fehler erkennen.
• Lösung: try, catch in der Testmethode.
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Testen von Exceptions (2)
public class MoneyTest extends TestCase {
//...
public void testNegative() {
try {
new Money(-12, “EUR“);
fail("IllegalArgumentException erwartet!");
} catch (IllegalArgumentException expected) { }
}
}
public class MoneyTest extends TestCase {
//...
public Money(int amount, String currency) {
if(amount < 0) {
throw new IllegalArgumentException("Negative amount");
}
this.amount = amount;
this.currency = currency;
}
}
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Zusammenfassung
• Ein einfach strukturiertes jedoch sehr gut durchdachtes Werkzeug.
• Tests verlangen, nachdem sie einmal verfasst wurden vom Entwickler
kein aktives Denken (außer im Fehlerfall).
• Tests unterstützen den Entwickler während der ganzen Entwicklung:
– Wir merken schnell wann Code aufhört zu funktionieren.
– Wir müssen die Tests nicht im eigentlichen Code integrieren,
demnach entfallen evtl. Doppelarbeiten die mit Löschen von Tests
(Standardausgabe) zusammenhängen.
• Fazit: sehr empfehlenswert.
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Schlussbemerkung
• JUnit wurde durch JUnit geprüft.
• Der TestTest hat funktioniert.
• Mehr Informationen:
– www.junit.org (englisch)
– www.frankwestphal.de/UnitTestingmitJUnit.html (deutsch)
JUnit - eine Testumgebung für Java, Informatikseminar WS02/03, Kamil Kube
Herunterladen