Ich nehme derzeit an einer Akademie zum Java Fullstack Software Engineer teil. In den kommenden Wochen möchte ich hier meine Mitschrift, so gut es geht, aufzeichnen und mitteilen. Hier ist das, was ich vom ersten Tag in Block 4 gelernt und behalten habe:
JUnit 4
Wir erstellen ein neues Java Projekt (mit Maven und der Quickstart-Vorlage) und nennen es "test-driven-development"

Wir sehen in der Ordnerstruktur einen main- und einen test-Ordner:

Dort befindet sich, durch unsere Maven-Vorlage, jeweils auch schon eine Klasse im main und im testOrdner. Sie unterscheiden sich durch die fehlende main-Methode und einer zusätzliche Annotation in der test-Klasse:

Die @Test-Annotation hat eine Bedeutung, nämlich, dass sie ausgeführt werden kann, wenn man die Testclasse startet. Der Compiler (Junit) erkennt an dem Java-Namen "…Test".class dass nicht die Main-Methode sondern die @Test-Annotation gesucht und gestartet werden soll. Die anschließende Methode liefert immer ein void zurück (also nichts).

Die vier Tests in diesem Beispiel werden parallel durchgeführt (zumindest in JUnit 4). Daher wird die Reihenfolge von Alpha, Beta, Gamma und Delta durcheinander gebracht (je nachdem welcher Thread zuerst fertig ist). Siehe links unten.

Die "@Before" Annotation bewirkt, dass sie vor jedem Einzeltest läuft. @After läuft jeweils hinter jeder Methode.
Die "assert..." (Annahme) Methode macht den eigentlichen Test. Man übergibt ihr (einen) Testparameter, das zu erwartende Ergebnis und bei Bedarf auch die maximale Abweichung. Passt das Ergebnis, ist der Test bestanden (passed), ansonsten ist er failed.
So sieht ein sauberer Testfehler (failed) aus. Wir erwarten ein Ergebnis von 0.5, bekommen aber 0:

Regressionstests: ist der Test, den ich dann durchführe, wenn ich eine Änderung am Code gemacht habe. Man probiert denselben Test immer wieder mit anderen Parametern aus.

Negative Tests mit Exceptions
In der Klasse Kehrwert soll eine Exception geworfen werden, wenn eine Null übergeben wird. Daher erstellen wir die Exception "KehrwertException".

In der Testklasse KehrwertTest.java wird dann eine neue Methode hinzugefügt, die abfragt, ob eine Exception geworfen wird, wenn eine Null übergeben wird. Diese Exception wird immer weiter nach oben geworfen, bis JUnit sie auffängt. Deshalb fragen wir JUnit mit "(expected = KehretException.class)" ob es eine Exception erhält. Wenn ja, ist der Test bestanden:

JUnit 5
Wir ändern die pom.xml von JUnit 4 auf JUnit 5

Hier zum Mitschreiben (die letzte Dependency mussten einige von uns (warum auch immer) hinzufügen, damit es klappt):
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.8.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.8.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> <version>5.8.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-params</artifactId> <version>5.8.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.platform</groupId> <artifactId>junit-platform-runner</artifactId> <version>1.5.2</version> <scope>test</scope> </dependency>
Mit JUnit5 kann man die Tests in eine Reihenfolge bringen:

Man kann auch Test-Klassen ins Suiten zusammen fassen:

Hier der fertge Code für unseren Kreisrechner:
Klasse KreisRechner:
package de.firma.util;public class KreisRechner {
public double flaeche(double radius) throws RadiusException { if (radius == 0.0){ throw new RadiusException ("Ungültiger Wert"); } return Math.PI*radius*radius; } public double umfang (double radius) throws RadiusException { if (radius == 0.0){ throw new RadiusException ("Ungültiger Wert"); } return 2*Math.PI*radius; }
}
KreisRechnerTest:
package firma.de.util;import de.firma.util.KreisRechner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;import de.firma.util.RadiusException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;public class KreisRechnerTest {
private KreisRechner kr;@BeforeEach public void setUp(){ kr = new KreisRechner(); } @AfterEach public void tearDown() { kr = null; //Aufräumen // SOll-/Ist-Vergleich // Daten //Logging } @Test public void testflaeche(){ // assertEquals(3.14159265, kr.flaeche(1),0.001); //JUnit 4 try { Assertions.assertEquals(3.14159265, kr.flaeche(1), 0.001 ); //JUnit 5 } catch (RadiusException e) { e.printStackTrace(); } } @Test void testumfang(){ try { assertEquals(6.282, kr.umfang(1),0.01); } catch (RadiusException e) { e.printStackTrace(); } } @Test public void testRadiusException() { assertThrows(RadiusException.class, () -> { kr.umfang(-1); kr.flaeche(-1); }); }
}
RadiusException:
package de.firma.util;public class RadiusException extends Exception{
public RadiusException(String msg){
super(msg);
}
}
Parametrisierte Tests:
Bei parametrisierten Tests werden den Testmethoden Listen von Parametern übergeben, die idealerweise von der Fachabteilung kommen. Hier ein paar Beispiele:
Einfache Liste mit drei verschiedenen Argumenten:

Leere Werte Tests:

Enum-Klassen als Parameter-Quelle:

Streams als Parameter-Quelle:

Weitere Beispiele:





——————
Disclaimer
Alles was ich mitschrieb und verstanden habe ist ohne Gewähr.
Besten Dank an unseren
Trainer: Hans-Joachim Blanke blanke@4point.de

In den nächsten Tagen und Wochen geht's weiter, so: stay tuned!
Gruß, Achim Mertens