2021.06 2025.02

Modultests mit JUnit

MyCoRe bietet dedizierte Hilfsklassen und JUnit-Erweiterungen, die das Erstellen von Unit- und Integrationstests vereinfachen. Diese Utilities verwalten die Komplexität der MyCoRe-Umgebung (Konfiguration, Datenbank, Storage, Sessions), sodass sich Entwickler auf die Testlogik konzentrieren können.

Einleitung

MyCoRe stellt Utilities bereit, um das Erstellen von Unit- und Integrationstests mit dem JUnit-Framework zu vereinfachen. Diese Helfer übernehmen das Management der MyCoRe-Umgebung (Konfiguration, Datenbank, Storage, Sessions) und ermöglichen es Entwicklern, sich auf das Testen der Komponentenlogik zu konzentrieren. Dieses Dokument beschreibt den modernen Ansatz mit JUnit 5 (empfohlen für neue Tests) sowie die Unterstützung für das ältere JUnit 4.

JUnit 5 Support (Empfohlen)

Seit MyCoRe 2025.02 wird JUnit 5 mit dedizierten Erweiterungen unterstützt, die das Testen von MyCoRe-Komponenten vereinfachen. Das modulare Extension-Modell von JUnit 5 wird hierbei genutzt, um spezifische Aspekte der Testumgebung gezielt zu aktivieren. Tipps zur Migration von JUnit 4 auf JUnit 5 finden Sie hier im offiziellen JUnit 5 User Guide.

Basis-Setup mit @MyCoReTest

Die Annotation @MyCoReTest ist die grundlegende Annotation für JUnit 5 Tests in MyCoRe. Sie muss auf Testklassen angewendet werden, die eine MyCoRe-Laufzeitumgebung benötigen. Ihre Hauptaufgaben umfassen:

  • Initialisierung der Basis-MyCoRe-Umgebung.
  • Einrichtung eines temporären Verzeichnisses für MCR.Home.
  • Laden der Standard-MyCoRe-Konfigurationsproperties (mycore.properties).
  • Verwaltung des MCRSession-Lebenszyklus für jede Testmethode (Session wird vor dem Test verfügbar gemacht und danach geschlossen).
  • Ermöglicht das Setzen von test-spezifischen Konfigurationsproperties über @MCRTestConfiguration und @MCRTestProperty (siehe Beispiel).

Mit @MCRTestConfiguration können Properties auf Klassen- oder Methodenebene definiert oder überschrieben werden.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mycore.common.MCRTestConfiguration;
import org.mycore.common.MCRTestProperty;
import org.mycore.common.config.MCRConfiguration2;
import org.mycore.common.config.MCRConfigurationBase;
import org.mycore.common.config.MCRConfigurationException;
import org.mycore.test.MyCoReTest;

// Basis-Annotation für MyCoRe Tests
@MyCoReTest
// Test-spezifische Properties auf Klassenebene
@MCRTestConfiguration(
    properties = {
        // Property mit Klassennamen als Wert
        @MCRTestProperty(key = "MCR.Test.Class", classNameOf = MCRMyUnitTest.class),
        // Property mit String-Wert
        @MCRTestProperty(key = "MCR.myProperty", string = "junit-class-level")
    }
)
public class MCRMyUnitTest {

    @Test
    public void testClassProperty() {
        // Zugriff auf Property, das auf Klassenebene definiert wurde
        Assertions.assertEquals(
            "junit-class-level",
            MCRConfiguration2.getStringOrThrow("MCR.myProperty")
        );
        Assertions.assertEquals(
            MCRMyUnitTest.class.getName(),
            MCRConfiguration2.getStringOrThrow("MCR.Test.Class")
        );
    }

    // Zusätzliche / überschreibende Properties für diese Methode
    @Test
    @MCRTestConfiguration(
        properties = {
            // Überschreibt den Wert von "MCR.myProperty" nur für diesen Test
            @MCRTestProperty(key = "MCR.myProperty", string = "junit-method-level"),
            // Definiert ein leeres Property
            @MCRTestProperty(key = "MCR.emptyProperty", empty = true)
        }
    )
    public void testMethodProperty() {
        // Property von Methode überschreibt Property von Klasse
        Assertions.assertEquals(
            "junit-method-level",
            MCRConfiguration2.getStringOrThrow("MCR.myProperty")
        );
        Assertions.assertTrue(MCRConfigurationBase.getString("MCR.emptyProperty").isPresent());
        Assertions.assertTrue(MCRConfigurationBase.getString("MCR.emptyProperty").get().isEmpty());
        Assertions.assertThrowsExactly(MCRConfigurationException.class,
            () -> MCRConfiguration2.getStringOrThrow("MCR.emptyProperty"));
    }
}

Datenbank-Setup mit MCRJPAExtension

Wenn ein Test Datenbankzugriffe über JPA erfordert, aktivieren Sie zusätzlich zu @MyCoReTest die MCRJPAExtension mit @ExtendWith(MCRJPAExtension.class). Diese Erweiterung kümmert sich spezifisch um:

  • Einrichtung einer dedizierten In-Memory-Datenbank (H2), isoliert pro Testklasse bzw. Komponente.
  • Automatische Generierung des Datenbankschemas (create) basierend auf den JPA-Entitäten vor den Tests der Klasse.
  • Automatisches Löschen des Schemas (drop) nach allen Tests der Klasse.
  • Transaktionsmanagement: Startet eine Transaktion vor jeder Testmethode und führt am Ende einen Commit oder Rollback durch.
  • Datenbankbereinigung: Löscht die Inhalte aller Tabellen (TRUNCATE) nach jeder Testmethode, um Testisolation sicherzustellen.
  • Stellt sicher, dass ein EntityManager über MCREntityManagerProvider.getCurrentEntityManager() verfügbar ist.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mycore.backend.jpa.MCREntityManagerProvider;
import org.mycore.test.MCRJPAExtension; // Import der Extension
import org.mycore.test.MyCoReTest;

@MyCoReTest // Basis-Setup
@ExtendWith(MCRJPAExtension.class) // Zusätzliches JPA Setup aktivieren
public class MCRJPAExtensionTest {

    @Test
    public void testEntityManagerAvailable() {
        Assertions.assertNotNull(
            MCREntityManagerProvider.getCurrentEntityManager(),
            "EntityManager should be available"
        );
        // Hier können nun JPA Operationen durchgeführt werden
    }

    // Funktioniert auch in verschachtelten Tests
    @Nested
    class MCRJPAExtensionNestedTest {
        @Test
        public void testEntityManagerAvailableInNested() {
            Assertions.assertNotNull(
                MCREntityManagerProvider.getCurrentEntityManager(),
                "EntityManager should be available in nested test"
            );
        }
    }
}

Metadaten-Storage-Setup mit MCRMetadataExtension

Für Tests, die MyCoRe-Objekte über den MCRXMLMetadataManager speichern oder laden (z.B. Interaktion mit dem Dateisystem- oder SVN-basierten Storage), verwenden Sie zusätzlich zu @MyCoReTest die MCRMetadataExtension mit @ExtendWith(MCRMetadataExtension.class). Sie ist verantwortlich für:

  • Einrichtung von temporären Verzeichnissen für den Metadaten-Store (MCR.Metadata.Store.BaseDir) und optional für SVN (MCR.Metadata.Store.SVNBase).
  • Konfiguration der entsprechenden MyCoRe-Properties, um diese temporären Verzeichnisse zu nutzen.
  • Bereinigung dieser Verzeichnisse nach jeder Testmethode.
  • Sicherstellung, dass der MCRXMLMetadataManager initialisiert und einsatzbereit ist.

Hinweis: Die MCRJPAExtension ist für die reine Nutzung der MCRMetadataExtension nicht zwingend erforderlich, wird aber oft zusammen verwendet, wenn Tests sowohl Metadaten- als auch Datenbankoperationen umfassen (z.B. Speichern eines MCRObjects und der zugehörigen Datenbankeinträge).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mycore.common.MCRTestConfiguration;
import org.mycore.common.MCRTestProperty;
import org.mycore.common.config.MCRConfiguration2;
import org.mycore.datamodel.common.MCRXMLMetadataManager;
import org.mycore.datamodel.metadata.MCRObjectID;
import org.mycore.test.MCRJPAExtension; // Optional, wenn DB benötigt wird
import org.mycore.test.MCRMetadataExtension; // Import der Extension
import org.mycore.test.MyCoReTest;

@MyCoReTest // Basis-Setup
@ExtendWith(MCRJPAExtension.class) // Optional, nur wenn DB-Interaktion auch getestet wird
@ExtendWith(MCRMetadataExtension.class) // Metadaten-Storage Setup aktivieren
@MCRTestConfiguration(
    properties = {
        // Beispiel: Konfiguration für einen Metadatentyp
        @MCRTestProperty(key = "MCR.Metadata.Type.test", string = "true")
    })
public class MCRMetadataExtensionTest {

    @Test
    public void testXMLMetadataManagerAvailable() {
        // Property wird von MCRMetadataExtension gesetzt
        Path storeBaseDir = MCRConfiguration2.getOrThrow("MCR.Metadata.Store.BaseDir", Paths::get);
        System.out.println("Store BaseDir=" + storeBaseDir.toAbsolutePath());
        Assertions.assertTrue(Files.isDirectory(storeBaseDir), "Store base dir should exist");

        // MCRXMLMetadataManager ist nun konfiguriert und kann verwendet werden
        Assertions.assertFalse(
            MCRXMLMetadataManager.getInstance().exists(MCRObjectID.getInstance("MyCoRe_test_00004711")),
            "Object should not exist in the temporary store initially"
        );
        // Hier können nun Operationen wie store(), retrieve() etc. getestet werden
    }
}

JUnit 4 Support (Legacy)

Für ältere Tests oder Projekte, die noch nicht auf JUnit 5 migriert wurden, bietet MyCoRe Basisklassen für JUnit 4 Tests. Es wird jedoch empfohlen, neue Tests mit JUnit 5 und den oben beschriebenen Extensions zu schreiben.

Tests sollten von der Klasse MCRTestCase abgeleitet werden. Diese Klasse initialisiert das Basis-System und ermöglicht es, über die Methode getTestProperties() test-spezifische Properties zu setzen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.Map;
import org.junit.Test; // JUnit 4 Annotation
import org.mycore.common.config.MCRConfiguration2;
import org.mycore.test.MCRTestCase; // JUnit 4 Basisklasse

public class MCRMyLegacyUnitTest extends MCRTestCase { // Ableiten von MCRTestCase

    @Test // JUnit 4 Test-Annotation
    public void myTestMethod() {
        // Property aus getTestProperties() ist verfügbar
        MCRConfiguration2.getStringOrThrow("meinProperty");
    }

    // Überschreiben, um Test-Properties hinzuzufügen
    @Override
    protected Map<String, String> getTestProperties() {
        Map<String, String> properties = super.getTestProperties();
        properties.put("meinProperty", "wert");
        return properties;
    }
}

Mit MCRTestCase allein wird die Datenbank nicht initialisiert. Wird Datenbankzugriff benötigt, sollte die Testklasse stattdessen von MCRJPATestCase abgeleitet werden. Wenn Objekte über den MCRXMLMetadataManager gespeichert oder gelesen werden sollen (Dateisystem/SVN-Storage), sollte von MCRStoreTestCase abgeleitet werden.