Mit JfrUnit JUnit und Java Flight Recorder verbinden

05.07.2021

Mit dem Java Flight Recorder Framework bietet das JDK eine moderne und durchdachte Infrastruktur, um Einsichten in das Laufzeitverhalten einer Java Anwendung zu bekommen.

Auf dieser Infrastruktur baut die JUnit Library JfrUnit auf und ermöglicht es, durch den Java Flight Recorder erzeugte Events innerhalb von Tests auszuwerten.

Dieser Schritt klingt logisch – warum sollte man die generierten Event Daten also nicht auch dazu nutzen, um innerhalb eines Tests das Verhalten einer Anwendung zu validieren, wenn alternativ ein Mensch ähnliche Schlüsse aus den Daten manuell ziehen kann? In diesem Artikel möchte ich meine Gedanken und eine Idee teilen, wie die Library eingesetzt werden könnte. Die Grundlagen zum Java Flight Recorder kann man hier nachlesen: Vorstellung: Java Flight Recorder

JUnit Tests mit JfrUnit

Der Einsatz von JfrUnit in JUnit Tests ist entsprechend einfach, wie auch das grundlegende Prinzip hinter dem Java Flight Recorder. JfrUnit macht die während der Testausführung gesammelten Events über eine Schnittstelle verfügbar und erlaubt so, diese entsprechend zu filtern und zu validieren.

Nachdem man die Library als Dependency in seinem Build Tool (z.B. Maven) hinzugefügt hat, kann man auch schon direkt loslegen. Da zum Zeitpunkt dieses Artikels nur Snapshot Versionen zur Verfügung stehen, muss man eventuell noch ein Repository konfigurieren oder die Library selber in einem verfügbaren Repository ablegen. Mehr dazu findet man in der Readme auf der Github Seite unter dem Punkt Usage.

In unserem JUnit Test Code können wir nun die folgenden Schritte durchführen, um JfrUnit zu integrieren:

  1. Die Testklasse mit `@JfrEventTest` annotieren. Die Testklasse muss `public` sein.
  2. Ein (`public`) Feld hinzufügen, welches eine Kollektion der aufgezeichneten Events beinhaltet: `public JfrEvents jfrEvents = new JfrEvents();`
  3. Zu der `@Test` Methode Annotationen hinzufügen, die definieren, an welchen Flight Recorder Events wir interessiert sind. Zum Beispiel `@EnableEvent(„de.doubleslash.jfr.demo.Download“)`. Die Annotation kann mehrfach zu einer Methode hinzugefügt werden.
  4. Nach dem Ausführen der unter Test stehenden Funktion (nach dem „when“ Abschnitt im Sinne des GWT Patterns) muss gewartet werden, bis die Events verfügbar sind. Dies geschieht durch einen Aufruf von `jfrEvents.awaitEvents()`. Eventuell wird sich hier in der Zukunft noch etwas ändern. In der Readme findet man das folgende TODO bzw. Hinweis dazu:

    „This project requires OpenJDK 15 or later at runtime. Support for JDK 11 is on the roadmap, JfrUnit couldn’t rely on JFR event stream in this case though, but would have to read JFR events from a recording persisted to disk. A PR contributing this change would be very welcomed.“

  5. Zuletzt können wir nun unsere Asserts formulieren. Dazu kann man die von der JfrUnit zur Verfügung gestellten DSL verwenden. Zum Beispiel: ` assertThat(jfrEvents).contains(event(„de.doubleslash.jfr.demo.Download“).with(„length“, 1000000));`
import org.junit.jupiter.api.Test;

import dev.morling.jfrunit.EnableEvent;
import dev.morling.jfrunit.ExpectedEvent;
import dev.morling.jfrunit.JfrEventTest;
import dev.morling.jfrunit.JfrEvents;
import dev.morling.jfrunit.JfrEventsAssert;

@JfrEventTest
public class DemoTest {
	public JfrEvents jfrEvents = new JfrEvents();

	@EnableEvent(DownloadEvent.NAME)
	@Test
	void test() throws InterruptedException {
		// Given
		Demo demo = new Demo();
		// When
		demo.executeDownload("testuser", "/file.bin");
		// Then
		jfrEvents.awaitEvents();
		JfrEventsAssert.assertThat(jfrEvents)
				.contains(ExpectedEvent.event(DownloadEvent.NAME)
						.with("user", "testuser")
						.with("source", "/file.bin"));
	}
}

Einsatzszenarios

  • Effizienz Tests: JFR Events können quantitative Metriken enthalten. Zum Beispiel sogenannte „Duration Events“ beinhalten die Laufzeit eines Ereignisses und bilden damit die Performanz der Anwendung ab. Durch das Prüfen solcher Werte gegen einen Grenzwert können Qualitätsansprüche an die Effizienz der Anwendung geprüft werden. Man muss hier aufpassen, dass die Performanz auch sehr stark von der Ausführungsumgebung abhängen kann. Deshalb sollte für derartige Tests eine Umgebung vorhanden sein, welche möglichst der Zielumgebung entspricht.
  • Funktionale Tests: JFR Events bilden das Verhalten der Anwendung ab, kapseln dabei aber Implementierungsdetails weg. Damit können die Events dazu genutzt werden, das Verhalten der Anwendung und damit deren Qualität sicherzustellen.

Grundlegend ist die Voraussetzung, um Flight Recorder Events in Tests verwenden zu können, dass diese korrekt erzeugt werden. Bei durch die JVM vorgegebenen allgemeinen Events kann man sich darauf verlassen, bei selbstdefinierten anwendungsspezifischen Events ist man aber selber dafür verantwortlich. Die Idee ist jetzt, dass man auch die Erzeugung der Events in entsprechenden Unit-Tests abdecken sollte. Dann kann man sich auf der nächsten Ebene der Testpyramide, z.B. bei Integration Tests, entsprechend auf die Events verlassen. Die folgende Abbildung soll dies weiter veranschaulichen:

JfrUnit Test Szenarios
Abb. 1: JfrUnit Test Szenarios

Auf der linken Seite sehen wir, wie die Unit Tests jeweils die Funktion eines Services abdecken. Es wird sowohl geprüft, ob das entsprechende Event erzeugt, wie auch ob der Seiteneffekt ausgelöst wird. Mit dieser Basis können wir dann (wie auf der rechten Seite zu sehen) einen Integrations Test implementieren, der einen ganzen Prozess z.B. durch einen Webrequest ausgelöst, abdeckt. Die Besonderheit hier ist, dass wir dank den JFR Events nicht mehr darauf angewiesen sind, uns mit den Technologien der Seiteneffekte im Test zu beschäftigen (außer natürlich zu Zwecken der Isolation). Wir müssen nicht mehr auf die Datenbank zugreifen, um zu prüfen, ob z.B. der „Service A“ Schritt erfolgreich funktioniert hat, sondern können uns auf das Event verlassen.

Fazit

Auf mich macht das Java Flight Recorder Framework einen sehr durchdachten Eindruck und es bietet eine Möglichkeit, unsere Java Anwendungen besser zu verstehen. Was einfach zu verstehen ist, ist in der Regel auch einfacher zu testen. Man muss natürlich aufpassen, dass man das richtige Werkzeug für den richtigen Zweck verwendet. In der Testwelt gibt es viele Werkzeuge und Wege, um an Informationen zu kommen bzw. um Funktionen isoliert zu betrachten, z.B. Logs auswerten oder Spys.

  • Lognachrichten sind oft sehr dynamisch und schwer auszuwerten. JFR Events dagegen sind durch eine Javaklasse gestützt und dadurch typsicherer und eindeutiger.
  • Spys müssen an den notwendigen Stellen platziert werden und ermöglichen nur das Prüfen der Schnittstellen. JFR Events dagegen sind losgelöst von technischen Definitionen wie z.B. Methodensignaturen und können daher die Fachdomäne isoliert abbilden. Technische Details wie z.B. ob ein Optional oder eine List verwendet wurde, können ausgeblendet werden.

Da JFR erst mit neueren JDK Versionen wirklich das Potential entfalten kann (z.B. ab Java 14 durch JFR Event Streaming), und auch weil JFR erst mit neueren Versionen lizenztechnisch zugänglicher geworden ist, stehen viele Technologien Rund um JFR gefühlt noch ein wenig in den Kinderschuhen. So auch die JfrUnit Library. Zum Zeitpunkt dieses Artikels gab es noch keine offiziell veröffentlichte Maven Dependency und auch dem API, zum Abprüfen von Event Eigenschaften, fehlt die eine oder andere Option. Was ich aber bisher gesehen habe, gefällt mir sehr gut und inspiriert mich, den Java Flight Recorder als ein spannendes Werkzeug in meinem Java Werkzeugkoffer zu sehen.

 

Mehr zu Java Programmierungen erfahren

Zurück zur Übersicht

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

*Pflichtfelder

*