× Install ThecoreGrid App
Tap below and select "Add to Home Screen" for full-screen experience.
B2B Engineering Insights & Architectural Teardowns

JUnit 5-Migration in ein Monorepo mit 1,25 Millionen Zeilen

Die Migration von JUnit 4 auf JUnit 5 wurde zu einer architektonischen Herausforderung. Der Schlüssel ist Automatisierung und deterministische Code-Transformationen.

Das Problem zeigt sich nicht sofort — bis der Stack die Entwicklung einschränkt. In diesem Fall befand sich JUnit 4 im Wartungsmodus, was das Fehlen neuer Funktionen und das Wachstum der technischen Schulden bedeutete. Für ein großes Java-Monorepo wird dies zu einer systematischen Einschränkung: Neue Testmuster sind nicht verfügbar, und die Unterstützung alter Muster erfordert immer mehr Aufwand. Der Umfang verschärft die Situation — über 75.000 Testklassen und 1,25 Millionen Zeilen Code, die an Bazel gebunden sind, das ursprünglich JUnit 5 nicht unterstützt. Ein einfacher Austausch des Frameworks bricht hier die Pipelines und verringert die Release-Geschwindigkeit.

Die Lösung stellte sich als kompromissbehaftet und schrittweise heraus. Anstelle einer „Big Bang“-Migration wurde die Kompatibilität über die JUnit-Plattform gewählt. Dies ermöglichte das parallele Ausführen von JUnit 4- und JUnit 5-Tests unter Verwendung der Vintage- und Jupiter-Engine. Dieser Ansatz verringert das Risiko: Das System funktioniert weiterhin, während das neue Modell schrittweise das alte verdrängt. Der Trade-off ist offensichtlich — eine vorübergehende Verkomplizierung der Ausführungsschicht und ein Anstieg der operationellen Komplexität. Aber das ist der Preis für die Kontinuität der Entwicklung und das Fehlen von Ausfallzeiten.

Ein zentrales Element war die Automatisierung über OpenRewrite. Im Gegensatz zu generativem KI, das bei benutzerdefinierten Tests instabile Ergebnisse zeigte, wurde hier ein deterministischer Ansatz verwendet. OpenRewrite arbeitet mit dem semantischen Baum des Codes (AST), was eine präzise und vorhersehbare Transformation der JUnit 4-API in JUnit 5 ermöglicht. Ingenieure beschrieben Transformationsrezepte: Aktualisierung von Annotationen, Ersetzung von Legacy-Regeln, Umwandlung von parameterisierten Tests in ein Jupiter-kompatibles Format. Für interne Muster wurden benutzerdefinierte Regeln hinzugefügt, einschließlich der Unterstützung eigener Testläufer und Basisklassen.

Um partielle und inkonsistente Änderungen zu vermeiden, wurden Vorbedingungen hinzugefügt. Diese filtern nicht unterstützte Fälle heraus und garantieren, dass die Datei entweder vollständig migriert oder unberührt bleibt. Dies verringert das Risiko von „kaputten“ Tests und vereinfacht das Debugging. Zusätzlich wurde die Häufigkeit der Nutzung von Konstruktionen analysiert, um zunächst die am häufigsten verwendeten Muster abzudecken und die Effizienz der Automatisierung zu erhöhen.

Die Orchestrierung in diesem Maßstab ist eine eigene Herausforderung. Ein internes Tool namens Shepherd verwaltete die Transformationen über Tausende von Bazel-Zielen parallel. Es generierte Diffs und führte diese durch CI, einschließlich Unit- und Integrationstests. Dies schuf einen geschlossenen Prüfzyklus: Jede Änderung wurde nur akzeptiert, wenn die Verhaltenskorrektheit gewahrt blieb. Eine solche Pipeline verwandelt die Migration faktisch in einen verwalteten Änderungsfluss und nicht in eine einmalige Operation.

Die Ergebnisse können nicht über klassische Leistungskennzahlen bewertet werden — in den Ausgangsdaten werden sie nicht angegeben. Aber der architektonische Effekt ist klar. Das System erhielt einen modernen Test-Stack mit modularer Architektur und besserer Erweiterbarkeit. Dabei konnte eine Unterbrechung der Entwicklung und eine manuelle Überarbeitung von Hunderttausenden von Tests vermieden werden. Zusätzlich wurde das Fundament für zukünftige Transformationen gelegt: Migrationen zu Spring Boot 3, der Verzicht auf Guava zugunsten standardmäßiger APIs und der Übergang von Joda-Time zu java.time werden bereits in Betracht gezogen.

Die wichtigste Erkenntnis ist, dass bei Aufgaben dieser Größenordnung nicht das Werkzeug, sondern die Eigenschaften des Prozesses entscheidend sind. Determinismus, Kompatibilität und schrittweise Implementierung sind wichtiger als Geschwindigkeit. Generative Ansätze sind dort im Nachteil, wo Vorhersehbarkeit entscheidend ist. Und ein Monorepo mit strenger Integration in CI/CD erfordert nicht nur ein Migrationsskript, sondern eine vollständige Architektur für Änderungen.

Lesen

×

🚀 Deploy the Blocks

Controls: ← → to move, ↑ to rotate, ↓ to drop.
Mobile: use buttons below.