JUnit 5 – Aufbruch in eine neue Generation des Frameworks

Teil 1: Testing mit Assertions und Assumptions

Von: Sven Ruppert

Unit-Testing ist eine fundamentale Technik der Softwareentwicklung, die darauf abzielt, einzelne Module oder Einheiten eines Programms isoliert zu testen, um ihre Korrektheit sicherzustellen. Diese Herangehensweise stellt sicher, dass jede Komponente erwartungsgemäß funktioniert, was die Softwarequalität erheblich verbessert. Besonders in agilen Entwicklungsumgebungen spielt Unit-Testing eine zentrale Rolle, da es eine kontinuierliche Integration von Codeänderungen erleichtert und Entwickler dabei unterstützt, Regressionen frühzeitig zu identifizieren.

Die Bedeutung von Unit-Tests wurde mit der Verbreitung von Test-Driven Development noch verstärkt, da hierbei die Testfälle bereits vor der Implementierung der eigentlichen Funktionalität erstellt werden. JUnit ist eines der am weitesten verbreiteten Frameworks für Unit-Tests in der Java-Welt. Mit JUnit 5 wurde eine neue Generation dieses Test-Frameworks eingeführt, welche die Flexibilität und Erweiterbarkeit erheblich verbessert.

Der Einstieg in das Unit-Testing mit JUnit 5 beginnt mit der Definition der Dependencies in der pom.xml (unter Verwendung von Maven, siehe Maven Central: org.junit.jupiter) und der Erstellung eines ersten Tests. Ein einfacher Testfall besteht typischerweise aus einer Methode, die mit der Annotation @Test versehen ist. In dieser Methode wird das Verhalten einer bestimmten Funktionalität überprüft. Ein typischer erster Test überprüft beispielsweise eine mathematische Operation oder eine einfache Geschäftslogik. Die erwarteten Ergebnisse werden mit den tatsächlich zurückgegebenen Werten verglichen. Dazu werden verschiedene Assertions verwendet, die das Fundament jeder Testmethode bilden. Das Schreiben eines ersten Tests verdeutlicht den grundsätzlichen Ablauf von Unit-Tests: Vorbereitung der Testdaten, Ausführung der zu testenden Methode und schließlich die Verifikation der erwarteten Ergebnisse. Die Trennung dieser Schritte erhöht die Lesbarkeit und Wartbarkeit der Tests.

Hier ein einfaches Beispiel für das Grundgerüst eines Tests in Java:

@Test

void testMethodenName() {

//Hier kommt der Test hin.

}

Assertions sind essenziell für Unit-Tests, da sie sicherstellen, dass der getestete Code das erwartete Verhalten zeigt. In JUnit 5 stehen verschiedene Assertions zur Verfügung, um eine Vielzahl von Testszenarien abzudecken. Die grundlegendste Assertion ist assertEquals, die überprüft, ob der erwartete und der tatsächliche Wert übereinstimmen. Eine weitere häufig verwendete Methode ist assertTrue, die sicherstellt, dass eine Bedingung erfüllt ist. Ähnlich verhält es sich mit assertFalse, die auf das Gegenteil testet. Für Tests, die sicherstellen, dass eine bestimmte Methode eine Exception wirft, eignet sich assertThrows. Diese Methoden erlauben eine präzise Kontrolle über das Verhalten des Codes. Darüber hinaus gestattet JUnit 5 die Gruppierung von Assertions mit assertAll, wodurch mehrere Bedingungen innerhalb eines Tests überprüft werden können, ohne dass der Test bei einem ersten Fehlschlag abbricht. Die umfangreichen Möglichkeiten der Assertions unterstützen Entwickler dabei, robuste und aussagekräftige Tests zu schreiben.

Ein triviales Beispiel mit einer Assertion:

@Test

void testMenthodenName() {

int ergebnis = 4 + 3;

int erwartet = 7;

assertEquals(erwartet, ergebnis);

}

Neben Assertions spielen Assumptions eine bedeutende Rolle in Unit-Tests, insbesondere wenn bestimmte Testfälle nur unter bestimmten Bedingungen ausgeführt werden sollen. Assumptions sind nützlich, um Tests zu steuern, die unter bestimmten Voraussetzungen sinnvoll sind. Eine weit verbreitete Methode ist assumeTrue, die sicherstellt, dass ein Test nur ausgeführt wird, wenn eine gegebene Bedingung erfüllt ist. Ist sie das nicht, wird der Test übersprungen, anstatt zu fehlschlagen. Dies ist beispielsweise bei Tests relevant, die von spezifischen Umgebungsvariablen oder Systemkonfigurationen abhängig sind. Ähnlich verhält es sich mit assumeFalse, die den gegenteiligen Fall abdeckt. Diese Mechanismen erlauben es, flexible Tests zu schreiben, die sich dynamisch an die jeweilige Umgebung anpassen, ohne unnötige Fehlschläge zu verursachen. Assumptions tragen somit zur Stabilität und Wartbarkeit einer Test-Suite bei.

Dieser Test soll nicht ausgeführt werden, wenn das Betriebssystem Windows ist:

@Test

void testMethodenName() {

*assumeFalse*(System

         .*getProperty*("os.name")

        .contains("Windows"),

    "Dieser Test wird auf Windows übersprungen");

int ergebnis = 4 + 3;

int erwartet = 7;

assertEquals(erwartet, ergebnis);

}

Ein weiteres zentrales Konzept in JUnit 5 ist der Test Instance Lifecycle, der das Verhalten der Testinstanzen steuert. Standardmäßig wird für jede Testmethode eine neue Instanz der Testklasse erstellt, was die Isolation zwischen den Tests garantiert. Dies kann jedoch in bestimmten Situationen unerwünscht sein, insbesondere wenn eine aufwändige Initialisierung der Testobjekte erforderlich ist. Mit der Annotation @TestInstance kann der Lifecycle angepasst werden, sodass eine einzelne Instanz für alle Tests einer Klasse wiederverwendet wird. Dies kann nützlich sein, um Zustandsabhängigkeiten innerhalb einer Testklasse beizubehalten, beispielsweise wenn aufwendige Ressourcen einmalig initialisiert werden sollen.

Zusätzlich bietet JUnit 5 mit der Annotation @BeforeEach eine Möglichkeit, Vorbereitungen vor jeder Testmethode durchzuführen. Methoden, die mit @BeforeEach annotiert sind, werden vor jedem einzelnen Testfall ausgeführt, was insbesondere für die Initialisierung von Objekten oder das Zurücksetzen von Zuständen hilfreich sein kann. Dies stellt sicher, dass jeder Test mit einer sauberen Ausgangslage beginnt, wodurch unerwartete Wechselwirkungen zwischen Testfällen vermieden werden. Der Test Instance Lifecycle beeinflusst damit nicht nur die Performance, sondern auch die Struktur und Organisation der Tests. Die Entscheidung für einen bestimmten Lifecycle sollte gut durchdacht sein, um sowohl Isolation als auch Effizienz zu gewährleisten.

@BeforeEach
void setUp() {
    calculator = new Calculator();
    System.out.println("Neuer Calculator für den Test initialisiert.");
 }

 @Test
void testAddition() {
    int result = calculator.add(2, 3);
    assertEquals(5, result, "2 + 3 sollte 5 ergeben");
 }

 @Test
 void testSubtraction() {
   int result = calculator.subtract(5, 2);
   assertEquals(3, result, "5 - 2 sollte 3 ergeben");
 }
}

// Eine einfache Calculator-Klasse für das Beispiel
class Calculator {
    int add(int a, int b) {
    return a + b;
 }

int subtract(int a, int b) {
    return a - b;
 }

Zusammenfassend bietet JUnit 5 eine leistungsstarke und flexible Umgebung für Unit-Tests in Java. Durch die Verwendung von Assertions können Entwickler sicherstellen, dass ihr Code wie erwartet funktioniert. Assumptions ermöglichen eine dynamische Steuerung von Testfällen, sodass diese nur unter passenden Bedingungen ausgeführt werden. Der Test Instance Lifecycle bietet zusätzliche Konfigurationsmöglichkeiten, um die Testausführung an spezifische Anforderungen anzupassen. Die Kombination dieser Konzepte ermöglicht eine strukturierte und effiziente Teststrategie, die zur langfristigen Qualitätssicherung von Software beiträgt. JUnit 5 stellt damit eine essenzielle Grundlage für moderne Softwareentwicklung dar und fördert eine testgetriebene Herangehensweise an die Implementierung von Code.

Happy Coding!

Du möchtest noch mehr über Unit-Testing erfahren? Die heise academy hilft dir weiter! In seinem Videokurs "JUnit 5 für Einsteiger" führt Sven Ruppert dich in die Welt des Testens mit Java ein.

Über den Autor: Sven Ruppert

Sven programmiert seit 1996 Java in Industrieprojekten, seit über 15 Jahren weltweit Java in Branchen wie Automobil, Raumfahrt, Versicherungen, Banken, der UNO und der Weltbank. Seit zehn Jahren ist er als Sprecher auf Konferenzen und Community-Events in Ländern von Amerika bis Neuseeland. Er hat als Developer Advocate für JFrog und Vaadin gearbeitet und schreibt regelmäßig Artikel für IT-Magazine und Technologieportale.


Diese Beiträge könnten dich auch interessieren: