Ситуация: интеграционный тест стабильно проходит локально, но в CI падает примерно раз в 30 прогонов. Перезапуск сборки возвращает зелёный статус, и причина остаётся неизвестной. Прежде чем что-то исправлять, нужно собрать факты о падении. Без этого любые правки — гадание.

Шаг 1. Зафиксируйте условия падения

Не перезапускайте сборку сразу. Сохраните лог упавшего прогона, номер сборки, список тестов, которые выполнялись до и после упавшего, и версию окружения (образ CI, переменные, состояние БД). Если CI позволяет, включите сохранение артефактов при падении: дампы, скриншоты, логи приложения. Это даст материал для анализа.

Шаг 2. Проверьте порядок запуска тестов

JUnit 5 по умолчанию не гарантирует порядок методов в классе, а между классами порядок может зависеть от сборщика. Случайный порядок — частая причина «плавающих» падений, если тесты делят состояние. Посмотрите, не включена ли случайная сортировка (например, через junit.jupiter.testmethod.order.default). Запустите тесты с фиксированным порядком и сравните с результатами CI. Если падения исчезают, причина в изоляции.

Шаг 3. Ищите общее состояние

Интеграционные тесты часто работают с одной базой данных. Если один тест оставляет данные, а другой от них зависит, падение может проявиться только при определённой последовательности. Проверьте, очищается ли БД между тестами, используются ли транзакции с откатом, нет ли асинхронных операций, завершающихся после теста. Обратите внимание на тесты, которые меняют схему или глобальные настройки.

Шаг 4. Воспроизведите падение локально

Попробуйте запустить тесты в том же порядке, что и в CI, или с случайной сортировкой. Используйте профиль, максимально приближенный к CI: те же версии зависимостей, тот же контейнер БД, те же переменные окружения. Если падение воспроизводится, сузьте круг: запускайте подмножества тестов, пока не найдёте пару, которая конфликтует.

Шаг 5. Проверьте асинхронность и таймауты

В CI окружение медленнее или имеет другие лимиты. Тесты, которые полагаются на фиксированные задержки или не дожидаются завершения асинхронных операций, могут падать случайно. Используйте явные ожидания (Awaitility, @Timeout) вместо Thread.sleep. Убедитесь, что пулы потоков и соединений корректно закрываются.

Шаг 6. Документируйте и исправляйте причину, а не симптом

Когда причина найдена, зафиксируйте её в задаче и добавьте тест, который воспроизводит проблему. Исправление должно устранять корень: изоляция данных, детерминированный порядок, корректная очистка. Перезапуск сборки — это не решение, а способ скрыть проблему. Ведите статистику нестабильных тестов, чтобы видеть, какие из них требуют внимания.

  • Сохраняйте артефакты при падении.
  • Проверяйте порядок запуска и изоляцию состояния.
  • Воспроизводите условия CI локально.
  • Устраняйте причину, а не перезапускайте сборку.