JUnit ist ein Java-Werkzeug, mit dem Java-Klassen automatisiert getestet werden können (ursprünglich von Kent Beck und Erich Gamma konzipiert).
Während bei Black-Box-Systemtests und Akzeptanztests die Software als Ganzes und ohne Kenntnis der Internas getestet wird (z.B. mit Fit), wird JUnit im Java-Umfeld eingesetzt für Unit-, Komponenten- und White-Box-Tests, bei denen die Internas der zu testenden Komponente bekannt sind.
Zu den zu testenden Klassen werden Testklassen mit mehreren Testmethoden erzeugt. Mehrere Testklassen können zu Test-Suiten zusammengefasst werden. Das TestRunner-Tool führt die Tests aus und berichtet kummulativ die Ergebnisse.
Besonders moderne "agile" Vorgehensmodelle zum Softwareentwicklungsprozess (z.B. XP, Extreme Programming) werben mit "Test Driven Development" ("TDD") und beführworten extensives Testen und das Erstellen der Tests bevor mit der eigentlichen Kodierung begonnen wird.
Legen Sie ein Projektverzeichnis an (z.B. D:\MeinWorkspace\JUnitTest1), darunter die Verzeichnisse bin, lib, src und test, und unter den letzten beiden jeweils meinpackage:
[\MeinWorkspace\JUnitTest1] |- [bin] |- [lib] |- [src] | '- [meinpackage] '- [test] '- [meinpackage]
Die Testklassen werden in einem getrennten Verzeichnisbaum (unter 'test') abgelegt, damit sie später leicht weggelassen werden können. Aber die Package-Struktur des 'src'-Verzeichnisses sollte identisch auch im 'test'-Testverzeichnis wiederholt werden, damit die Testklassen auch Zugriff auf protected-Elemente der zu testenden Klassen haben.
Als zu testende Java-Klasse soll das folgende einfache Beispiel dienen,
welches von Zahlen wahlweise das Quadrat oder die Wurzel ermittelt.
Speichern Sie im
src\meinpackage-Verzeichnis
die folgende Nutzklassendatei: MeineKlasse.java
package meinpackage; public class MeineKlasse { private String job; public String getJob() { return job; } public void setJob( String job ) { this.job = job; } public double myMethod( double x ) throws Exception { if( "Quadrat".equalsIgnoreCase( job ) ) return x * x; if( "Wurzel".equalsIgnoreCase( job ) ) return Math.sqrt( x ); throw new Exception( "Fehler: Aufgabe nicht korrekt definiert." ); } }
Speichern Sie im test\meinpackage-Verzeichnis die folgende Testklassendatei: MeineKlasseTest.java
package meinpackage; import junit.framework.TestCase; public class MeineKlasseTest extends TestCase { MeineKlasse meineKlasse1; @Override public void setUp() throws Exception { meineKlasse1 = new MeineKlasse(); assertEquals( "Anfangs darf kein Job gesetzt sein.", null, meineKlasse1.getJob() ); } @Override public void tearDown() throws Exception { meineKlasse1 = null; } public void testGetAndSetJob() { meineKlasse1.setJob( "Quadrat" ); assertEquals( "Job muss 'Quadrat' sein.", "Quadrat", meineKlasse1.getJob() ); } public void testDoJobs() throws Exception { meineKlasse1.setJob( "Quadrat" ); assertTrue( "Quadrat von '4' muss '16' sein.", 16. == meineKlasse1.myMethod( 4 ) ); meineKlasse1.setJob( "Wurzel" ); assertTrue( "Wurzel von '4' muss '2' sein.", 2. == meineKlasse1.myMethod( 4 ) ); meineKlasse1.setJob( null ); try { meineKlasse1.myMethod( 4 ); fail( "Exception muss geworfen werden, da kein korrekter Job gesetzt." ); } catch( Exception ex ) {/*ok*/} } }
In 'setUp()' können Initialisierungen durchgeführt werden.
In 'tearDown()' kann aufgeräumt werden (z.B. Datenbankverbindungen schließen).
Die Testmethoden 'test...()' müssen folgende Bedingungen erfüllen,
um (per Reflection) als Testmethoden erkannt zu werden:
- Der Name muss mit 'test' beginnen
- Sie dürfen keine Parameter haben
- Sie müssen 'public' und dürfen nicht 'static' sein
Doku zu den 'assert...()'- und 'fail()'-Methoden finden Sie in der Doku zur Klasse Assert. Beachten Sie die Vorgehensweise, wie mit 'fail()' im 'try'-Block das Werfen von Exceptions kontrolliert wird.
Die Projektstruktur sieht jetzt so aus:
cd \MeinWorkspace\JUnitTest1
tree /F
[\MeinWorkspace\JUnitTest1] |- [bin] |- [lib] | '- junit-4.8.2.jar |- [src] | '- [meinpackage] | '- MeineKlasse.java '- [test] '- [meinpackage] '- MeineKlasseTest.java
Öffnen Sie ein Kommandozeilenfenster ('Windows-Taste' + 'R', 'cmd'), compilieren Sie und führen Sie die Tests aus:
cd \MeinWorkspace\JUnitTest1
javac -cp bin;lib/* -d bin src/meinpackage/*.java
javac -cp bin;lib/* -d bin test/meinpackage/*.java
java -cp bin;lib/* junit.textui.TestRunner meinpackage.MeineKlasseTest
Sie erhalten:
OK (2 tests)
Bauen Sie in der zu testenden Klasse Fehler ein, um Fehlermeldungen beobachten zu können, und führen Sie die Tests erneut aus.
Falls Sie viele Testklassen haben, können Sie diese zusammenfassen. Speichern Sie im test\meinpackage-Verzeichnis die folgende Test-Suite-Klasse: AllTests.java
package meinpackage; import junit.framework.Test; import junit.framework.TestSuite; public class AllTests extends TestSuite { public static Test suite() { TestSuite mySuite = new TestSuite( "Meine Test-Suite" ); mySuite.addTestSuite( meinpackage.MeineKlasseTest.class ); // ... weitere Testklassen hinzufügen return mySuite; } }
Sie können auch zum Beispiel pro package alle Testklassen zu einer TestSuite zusammengefassen, und dann diese TestSuites nochmal zu einer TestSuite zusammengefassen.
Führen Sie die Tests aus:
cd \MeinWorkspace\JUnitTest1
javac -cp bin;lib/* -d bin src/meinpackage/*.java
javac -cp bin;lib/* -d bin test/meinpackage/*.java
java -cp bin;lib/* junit.textui.TestRunner meinpackage.AllTests
Sie erhalten wieder:
OK (2 tests)
Diese Option wird normalerweise nicht genutzt, aber der Vollständigkeit halber soll auch vorgestellt werden, wie Sie den TestRunner in einer main()-Methode verwenden könnten. Speichern Sie im test\meinpackage-Verzeichnis die folgende Klasse: TestMain.java
package meinpackage; public class TestMain { public static void main( String[] args ) { junit.textui.TestRunner.run( meinpackage.MeineKlasseTest.class ); } }
Führen Sie die Tests aus:
cd \MeinWorkspace\JUnitTest1
javac -cp bin;lib/* -d bin src/meinpackage/*.java
javac -cp bin;lib/* -d bin test/meinpackage/*.java
java -cp bin;lib/* meinpackage.TestMain
Sie erhalten wieder:
OK (2 tests)
Erzeugen Sie im test\meinpackage-Verzeichnis die neue Test-Suite-Datei: AllTestsRepeated.java
package meinpackage; import junit.framework.Test; import junit.framework.TestSuite; import junit.extensions.RepeatedTest; public class AllTestsRepeated extends TestSuite { public static Test suite() { TestSuite mySuite = new TestSuite( "Meine Test-Suite" ); mySuite.addTestSuite( meinpackage.MeineKlasseTest.class ); // ... weitere Testklassen hinzufügen return new RepeatedTest( mySuite, 5 ); } }
Führen Sie die Tests aus:
cd \MeinWorkspace\JUnitTest1
javac -cp bin;lib/* -d bin src/meinpackage/*.java
javac -cp bin;lib/* -d bin test/meinpackage/*.java
java -cp bin;lib/* junit.textui.TestRunner meinpackage.AllTestsRepeated
Die Tests werden jetzt fünfmal wiederholt. Das macht bei diesem einfachen Test natürlich keinen Sinn, aber es gibt viele Situationen, bei denen Fehler erst beim wiederholten Versuch auftreten, zum Beispiel bei konkurrierenden Threads, Message Queues oder bei Einbindung externer Komponenten über Netzwerk- oder Datenbankverbindungen.
Das folgende Beispiel sucht mit 1 beginnend fortlaufend durchnummerierte Properties-Dateien
mit den Namen 'Test1.properties', 'Test2.properties' usw. und zählt sie.
Entsprechend der Anzahl der gefundenen Properties-Dateien wird genau so oft der Test wiederholt
mit für jeden Test neue geladenen Testparametern aus der jeweils entsprechenden Properties-Datei.
Speichern Sie im
test\meinpackage-Verzeichnis
die folgende Testklassendatei: MeineKlassePropTest.java
package meinpackage; import java.io.FileInputStream; import java.util.Properties; import junit.extensions.RepeatedTest; import junit.framework.*; public class MeineKlassePropTest extends TestCase { static int counterForUsedPropFile = 0; // counter for properties files public static Test suite() { counterForUsedPropFile = 0; // Count properties files: int i = 0; Properties prop = new Properties(); while( true ) { try { prop.load( new FileInputStream( "./test/Test" + ++i + ".properties" ) ); } catch( Exception ex ) { break; } } // Create RepeatedTest, for each properties file one test: TestSuite mySuite = new TestSuite( "TestRepeater" ); mySuite.addTestSuite( MeineKlassePropTest.class ); // If no properties file found, we set '1' for getting an error // message with 'fail()' in 'testReadParmsFromPropFile()': return new RepeatedTest( mySuite, ( 0 < --i ) ? i : 1 ); } public void testReadParmsFromPropFile() throws Exception { // Read parameters from properties file: String propFileName = "./test/Test" + ++counterForUsedPropFile + ".properties"; Properties prop = new Properties(); try { prop.load( new FileInputStream( propFileName ) ); } catch( Exception ex ) { fail( "Fehler: Properties-Datei '" + propFileName + "' fehlt. " ); } String job = prop.getProperty( "job" ); double val = Double.parseDouble( prop.getProperty( "val" ) ); double rslt = Double.parseDouble( prop.getProperty( "rslt" ) ); // Test 'MeineKlasse': MeineKlasse meineKlasse1 = new MeineKlasse(); meineKlasse1.setJob( job ); if( "Quadrat".equalsIgnoreCase( job ) || "Wurzel".equalsIgnoreCase( job ) ) { assertEquals( job + " von '" + val + "': ", rslt, meineKlasse1.myMethod( val ), 0.001 ); } else { fail( "'job' muss 'Quadrat' oder 'Wurzel' sein, ist aber '" + job + "'." ); } } }
Sie benötigen zusätzlich Properties-Dateien. Speichern Sie im Unterverzeichnis 'test' die beiden folgenden Properties-Dateien:
Test1.properties
job=Wurzel val=64 rslt=8
Test2.properties
job=Quadrat val=64 rslt=4096
Ergänzen Sie weitere 'TestXX.properties'-Dateien.
Führen Sie die Tests aus:
cd \MeinWorkspace\JUnitTest1
javac -cp bin;lib/* -d bin src/meinpackage/*.java
javac -cp bin;lib/* -d bin test/meinpackage/*.java
java -cp bin;lib/* junit.textui.TestRunner meinpackage.MeineKlassePropTest
Wesentlich komfortabler ist der Einsatz von JUnit, wenn Sie die gute Integration von JUnit in Eclipse nutzen. Dazu soll das letzte Projekt noch mal von Grund auf neu erstellt werden.
In maven.htm finden Sie sehr viele JUnit (und TestNG) verwendende Beispiele.