Продвинутый

Тестирование в CI: стратегия и оптимизация

Урок 4 из 5 в курсе CI/CD: автоматизация сборки и деплоя

Содержание курса (4/5)

Тестирование в CI: стратегия и оптимизация

Тесты в CI — это страховка перед merge. Но медленный пайплайн замедляет всю команду. Параллелизация, кэширование, правильная пирамида тестов — ключ к быстрому и надёжному CI.

Почему это важно: Пайплайн длиной 30 минут означает, что разработчик ждёт полчаса, прежде чем может мержить. За день это несколько часов простоя на команду. Быстрый CI (< 5 минут) — прямое ускорение разработки.

Главная идея

Пирамида тестов: много быстрых unit-тестов, умеренное количество integration, минимум E2E. Параллелизация и кэширование сокращают время. Flaky-тесты — главный враг доверия к CI.

Как это выглядит на практике

  1. Пайплайн запускается: 500 тестов, текущее время — 20 минут
  2. Анализ: 80% времени занимают 50 E2E-тестов с реальной БД
  3. Оптимизация 1: кэширование зависимостей — сохраняем 2 минуты
  4. Оптимизация 2: параллельный запуск тестов на 4 runner'ах — 20 мин → 6 мин
  5. Оптимизация 3: разделение на fast (unit) и slow (E2E) jobs — unit за 1 мин, блокирует PR
  6. Результат: фидбек за 2 минуты (unit + lint), полный прогон за 6 минут

Примеры кода

Параллельные тесты в GitHub Actions

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        # Разбиваем тесты на 4 параллельных группы
        ci_node_total: [4]
        ci_node_index: [0, 1, 2, 3]
    steps:
      - uses: actions/checkout@v4
      - uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true

      - name: Run tests (shard ${{ matrix.ci_node_index }})
        env:
          CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
          CI_NODE_INDEX: ${{ matrix.ci_node_index }}
        run: |
          # Разбиваем файлы тестов по группам
          TESTS=$(find spec -name '*_spec.rb' | sort | \
            awk "NR % $CI_NODE_TOTAL == $CI_NODE_INDEX")
          bundle exec rspec $TESTS

Fast и slow pipelines

# Быстрая проверка: 1-2 минуты, блокирует PR
jobs:
  fast:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ruby/setup-ruby@v1
        with: { bundler-cache: true }
      - run: bundle exec rubocop --parallel
      - run: bundle exec rspec spec/models spec/services
        # Только unit-тесты — быстро

  # Полный прогон: 5-10 минут, не блокирует merge
  full:
    runs-on: ubuntu-latest
    services:
      postgres: { image: 'postgres:16', env: { POSTGRES_PASSWORD: pw } }
    steps:
      - uses: actions/checkout@v4
      - uses: ruby/setup-ruby@v1
        with: { bundler-cache: true }
      - run: bin/rails db:setup
      - run: bundle exec rspec  # Все тесты

Что происходит под капотом

  • Параллелизация через matrix: каждый shard запускает свою часть тестов на отдельном runner'е
  • bundler-cache/npm cache сохраняет зависимости между запусками — экономия 1–5 минут
  • Flaky test: тест, который случайно падает (race condition, порядок запуска) — подрывает доверие к CI
  • Test splitting по времени: продвинутые инструменты (knapsack, split-tests) распределяют тесты по времени, а не по количеству
  • Required checks в GitHub: можно сделать fast обязательным для merge, full — информативным

Типичные ошибки и заблуждения

  • «Больше E2E-тестов = надёжнее» — E2E медленные и flaky, unit-тесты быстрые и стабильные
  • «Flaky-тесты можно просто перезапустить» — это маскирует проблему, нужно фиксить причину
  • «CI должен запускать ВСЕ тесты» — для быстрого фидбека достаточно unit + lint, полный прогон можно делать фоново

Ключевые выводы

  • Пирамида тестов: много unit → средне integration → мало E2E
  • Параллелизация через matrix — линейное ускорение пайплайна
  • Fast pipeline (< 2 мин) для быстрого фидбека, full pipeline для полной проверки

Термины урока

{:term=>"Flaky test", :definition=>"Нестабильный тест, который периодически падает без изменений в коде (race condition, timing, порядок запуска)"}
{:term=>"Test sharding", :definition=>"Разделение тестов на группы для параллельного запуска на нескольких runner'ах"}
{:term=>"Required check", :definition=>"CI-проверка, которая обязательна для merge PR в GitHub"}

Связь с работой backend-разработчика

Инвестируйте в скорость CI: каждая минута ожидания умножается на количество разработчиков и PR в день. Быстрый фидбек (< 2 минут) меняет workflow: разработчики охотнее создают маленькие PR и чаще мержат.

Мини-разбор реальной ситуации

Команда из 8 человек: 600 тестов, CI занимал 25 минут. Разработчики начинали другие задачи во время ожидания и теряли контекст. После оптимизации: lint + unit (required, 90 секунд) + full suite (параллельно на 4 runner'ах, 4 минуты). Среднее время до merge сократилось с 2 часов до 20 минут.

Что запомнить

  • Пирамида тестов: 70% unit, 20% integration, 10% E2E
  • Параллелизация через matrix — самый простой способ ускорить CI
  • Fast pipeline (обязательный, < 2 мин) + full pipeline (информативный, 5-10 мин)

Итог

Скорость CI — критическая метрика продуктивности команды. Пирамида тестов, параллелизация и разделение на fast/full pipelines позволяют получать фидбек за минуты, а не за десятки минут.

Комментарии к уроку

Войдите, чтобы оставить комментарий.

Пока нет комментариев — будьте первым.