Timeouts in JUnit 5

JUnit ist ein wichtiges Framework zum Testen von Java-Code. Mittlerweile hat es sich verändert und wurde stark verbessert. In diesem Blog-Post möchte ich die Anwendung von Timeouts in JUnit 4 und 5 beschreiben.

Es kommt gelegentlich vor, dass die Laufzeit einer Testmethode länger ist als erwartet. Um zu ermöglichen, dass ein Test in einem solchen Fall abbricht, werden Timeouts verwendet. Dabei soll der Test innerhalb der vorgegebenen Zeit beendet werden. Zuerst wird gezeigt, wie Timeouts in JUnit 4 benutzt werden.  Beispiel 1 zeigt, wie man Timeouts  auf Methodenebene verwendet:

import org.junit.Test;

public class TimeoutTestJunit_4 {
	
	@Test(timeout = 2000)
	public void shouldCheckTimeout() throws InterruptedException {
		Thread.sleep(1000);
	}

	@Test(timeout = 2000)
	public void shouldCheckTimeoutFailure() throws InterruptedException {
		Thread.sleep(3000);
	}
}

Die Ausgabe:

Hier geht es um das Attribute timeout, das die maximale Zeit für die Ausführung dieser beiden Methoden vorgibt.

Die erste Methode hier wurde erfolgreich mit einer Verzögerung von 1 s ausgeführt, während die zweite Methode wegen einer Zeitüberschreitung fehlgeschlagen ist.

Die Alternative dazu ist, eine Timeout Rule auf Klassenebene zu nutzen. So muss man wie in Beispiel 2 nicht an jeder Methode das Attribut timeout definieren:

public class TimeoutTestJunit_4_Rule {

	@Rule // Setting Timeout globally for a Test Class
	public Timeout timeout = new Timeout(2000, TimeUnit.MILLISECONDS);

	@Test
	public void shouldCheckTimeout() throws InterruptedException {
		Thread.sleep(1000);
	}

	@Test
	public void shouldCheckTimeoutFailure() throws InterruptedException {
	        Thread.sleep(3000);
	}
}

Die Ausgabe:

Hier sieht man das gleiche Ergebnis wie in Beispiel 1. Aus diesen beiden Beispielen ergibt sich, dass die Anwendung von Timeouts auf Methodenebene spezifischer ist. Dabei kann man mehrere Testmethoden unabhängig voneinander mit verschiedenen vorgegebenen Timeouts ausführen.

Im Vergleich zu Junit 4 bietet Junit 5 die Annotation @Timeout anstatt das Attribut timeout an. Beispiel 3 zeigt eine Anwendung dieser Annotation:

import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;

public class TimeoutTestJunit_5 {

	@Test
	@Timeout(2)
	public void shouldCheckTimeout() throws InterruptedException {
		Thread.sleep(1000);
	}

	@Test
	@Timeout(value = 2000, unit = TimeUnit.MILLISECONDS)
	public void shouldCheckTimeoutFailure() throws InterruptedException {
		Thread.sleep(4000);
	}

Das Zeitlimit kann man einfach bei der Definition der Annotation @Timeout in Sekunden als Default vorgeben. Außerdem ist es ebenfalls möglich, die Einheit der vorgegebenen Zeit anzugeben.

Die Ausgabe:

In diesem Fall ist zu sehen, dass die Ausführung der zweiten Methode wegen der Zeitüberschreitung abgebrochen wurde.

Außerdem bietet JUnit 5 noch zwei Assertion-Methoden zum Thema Timeouts, nämlich assertTimeout(timeout, executable) und assertTimeoutPreemptively(timeout, executable). Beispiel 4 zeigt eine Anwendung dieser beiden Methoden:

import static org.junit.jupiter.api.Assertions.assertTimeout;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import java.time.Duration;
import org.junit.jupiter.api.Test;

public class TimeoutTestJunit_5 {

	@Test
	public void shouldCheckAssertTimeout() throws InterruptedException {

		assertTimeout(Duration.ofSeconds(2), () -> {
			Thread.sleep(3000);
			System.out.println("This will be printed out!");
		});
	}

	@Test
	public void shouldCheckAssertTimeoutPreemptively() throws InterruptedException {

		assertTimeoutPreemptively(Duration.ofSeconds(2), () -> {
			Thread.sleep(3000);
			System.out.println("This will not be printed out!");
		});
	}
}

Die Ausgabe:

Bei assertTimeout wird das übergebene Runnable im selben Thread wie der Test ausgeführt. Dadurch wird das Runnable komplett ausgeführt, obwohl das Zeitlimit überschritten wurde. Im Gegensatz dazu wird das Runnable von assertTimeoutPreemptively in einem eigenen Thread ausgeführt. Sobald das Zeitlimit überschritten wurde, wird die Ausführung des Runnable abgebrochen.

Short URL for this post: https://wp.me/p4nxik-3zD
This entry was posted in Java and Quality and tagged , . Bookmark the permalink.

Leave a Reply