Ситуация: интеграционный тест стабильно проходит локально, но в 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 локально.
- Устраняйте причину, а не перезапускайте сборку.