Короткий ответ
Нужно искать источники недетерминированности, которые отличают CI от локальной машины: параллельное выполнение тестов, общие ресурсы между тестами, зависимость от времени/таймзоны, сетевые тайм-ауты до внешних сервисов, недетерминированный порядок выполнения и различия версий зависимостей/окружения (ОС, версия языка, часовой пояс раннера).
Как это работает подробнее
локальная машина обычно запускает тесты последовательно и с «тёплым» состоянием (прогретый кэш, запущенные сервисы), а CI-раннер — чистое окружение, часто с параллельными джобами и ограниченными ресурсами (CPU/память), что обнажает гонки состояний (race conditions), неправильные допущения о порядке тестов или неосвобождённые ресурсы (открытые порты, файлы, тестовые данные в общей БД).
Пример кода
example.txt
Run 1: ✓ test_order_processing (120ms)
Run 2: ✗ test_order_processing
TimeoutError: connection to redis:6379 timed out after 50ms
Run 3: ✓ test_order_processing (95ms)Что использовать на практике
- На практике. диагностику начинают с повторного прогона упавшего теста изолированно много раз (`--repeat` или аналог), включения подробных логов и таймингов, проверки использования shared-состояния (глобальные переменные, singleton-соединения, фиксированные порты). Полезно временно отключить параллелизацию тестов в CI, чтобы увидеть, исчезает ли проблема — это укажет на гонку состояний между тестами.
- Пример. Ожидаемое поведение после фикса: тест либо увеличивает таймаут обоснованно и добавляет retry с backoff для внешней зависимости, либо выясняется, что Redis-контейнер в CI стартует медленнее, чем локально, и в pipeline добавляется явный health check перед запуском тестов, после чего тест проходит стабильно 50+ раз подряд.
Подводные камни
- Частые ошибки. сразу помечают тест как `skip`/`flaky` и игнорируют, теряя сигнал о реальном баге (возможно, в проде тоже воспроизводится race condition); увеличивают таймауты «на всякий случай» без понимания первопричины, что просто маскирует проблему и замедляет пайплайн.