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

JUnit 5 миграция в монорепо на 1.25 млн строк

Миграция JUnit 4 на JUnit 5 стала задачей архитектурного масштаба. Ключ — автоматизация и детерминированные трансформации кода.

Проблема проявляется не сразу — до момента, когда стек начинает ограничивать развитие. В данном случае JUnit 4 находился в режиме поддержки, что означало отсутствие новых возможностей и рост технического долга. Для большой Java-монорепозитории это превращается в системное ограничение: новые паттерны тестирования недоступны, а поддержка старых требует всё больше усилий. Масштаб усугубляет ситуацию — более 75 000 тестовых классов и 1.25 млн строк кода, завязанных на Bazel, который изначально не поддерживает JUnit 5. Простая замена фреймворка здесь ломает пайплайны и снижает release velocity.

Решение оказалось компромиссным и поэтапным. Вместо «big bang» миграции выбрали совместимость через JUnit Platform. Это позволило запускать JUnit 4 и JUnit 5 тесты параллельно с использованием Vintage и Jupiter engine. Такой подход снижает риск: система продолжает работать, пока новая модель постепенно вытесняет старую. Trade-off очевиден — временное усложнение execution layer и рост операционной сложности. Но это плата за непрерывность разработки и отсутствие даунтайма.

Ключевым элементом стала автоматизация через OpenRewrite. В отличие от генеративного AI, который показал нестабильные результаты на кастомных тестах, здесь использовался детерминированный подход. OpenRewrite работает с семантическим деревом кода (AST), что позволяет точно и предсказуемо преобразовывать API JUnit 4 в JUnit 5. Инженеры описали рецепты трансформации: обновление аннотаций, замена legacy rules, перевод parameterized tests в Jupiter-совместимый формат. Для внутренних паттернов добавили кастомные правила, включая поддержку собственных test runners и базовых классов.

Чтобы избежать частичных и неконсистентных изменений, добавили precondition checks. Они отсекают неподдерживаемые кейсы и гарантируют, что файл либо полностью мигрирован, либо не тронут. Это снижает риск «битых» тестов и упрощает отладку. Дополнительно анализировали частоту использования конструкций, чтобы сначала покрыть наиболее распространённые паттерны и повысить эффективность автоматизации.

Оркестрация на этом масштабе — отдельная задача. Внутренний инструмент Shepherd управлял трансформациями по тысячам Bazel-таргетов параллельно. Он генерировал diff’ы и прогонял их через CI, включая unit и integration тесты. Это создавало замкнутый цикл проверки: любое изменение принималось только при сохранении поведенческой корректности. Такой pipeline фактически превращает миграцию в управляемый поток изменений, а не в разовую операцию.

Результаты нельзя оценить через классические метрики производительности — в исходных данных они не приводятся. Но архитектурный эффект понятен. Система получила современный тестовый стек с модульной архитектурой и лучшей расширяемостью. При этом удалось избежать остановки разработки и ручной переработки сотен тысяч тестов. Дополнительно был заложен фундамент для следующих трансформаций: уже рассматриваются миграции Spring Boot 3, отказ от Guava в пользу стандартных API и переход с Joda-Time на java.time.

Главный вывод — в задачах такого масштаба решает не инструмент, а свойства процесса. Детерминизм, совместимость и поэтапное внедрение оказываются важнее скорости. Генеративные подходы пока уступают там, где критична предсказуемость. А монорепозиторий с жёсткой интеграцией в CI/CD требует не просто скрипта миграции, а полноценной архитектуры изменений.

Читать

×

🚀 Deploy the Blocks

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