Где запускать каждый тест в вашем пайплайне

Вы пушите код и ждете. Проходит пять минут. Десять. Пайплайн все еще выполняется. Наконец, он падает — но ошибка в юнит-тесте, который должен был отработать за первые тридцать секунд. Вы только что потратили десять минут своего времени и вычислительных ресурсов впустую.

Это ключевое противоречие в проектировании пайплайнов: запустишь слишком много тестов рано — разработчики ждут обратную связь слишком долго. Запустишь слишком мало — критические ошибки проскальзывают на стейджинг или в продакшен. Решение не в том, чтобы запускать всё везде, а в том, чтобы разместить каждый тип теста на том этапе, где он дает максимум пользы при минимуме затрат.

Принцип: сначала быстро и дешево

Правило простое: быстрые и дешевые тесты запускайте рано. Медленные и дорогие — позже, и только после того, как ранние тесты прошли.

«Дешево» здесь означает низкое потребление вычислительного времени, минимальную настройку окружения и подготовку данных. Юнит-тест, который выполняется за миллисекунды и не требует базы данных, — дешевый. End-to-end тест, который поднимает полное окружение, загружает данные и симулирует действия пользователя, — дорогой.

«Быстро» означает время обратной связи. Разработчик должен узнать в течение минуты-двух, сломал ли его код что-то фундаментальное. Ждать тридцать минут, чтобы обнаружить элементарную логическую ошибку, неприемлемо.

Это не вопрос политики. Это вопрос пропускной способности. Чем быстрее разработчики получают обратную связь, тем быстрее они исправляют проблемы. Чем дороже запуск теста, тем реже его следует запускать.

По этапам: где какой тест разместить

Этап коммита: юнит-тесты и контрактные тесты

Следующая диаграмма обобщает рекомендуемое размещение тестов по этапам пайплайна:

flowchart TD A[Этап коммита] --> B[Этап сборки] B --> C[Этап стейджинга] C --> D[Этап продакшена] A --> A1[Юнит-тесты] A --> A2[Контрактные тесты] B --> B1[Интеграционные тесты] B --> B2[Контрактные тесты] C --> C1[End-to-End тесты] C --> C2[Smoke-тесты] D --> D1[Smoke-тесты] D --> D2[Синтетические транзакции]

Когда разработчик пушит код, первым делом запускаются юнит-тесты. Эти тесты быстрые, не требуют внешних зависимостей и проверяют, что отдельные поведения работают корректно. Хороший юнит-тест доказывает, что осмысленное поведение — с точки зрения вызывающего кода или пользователя — дает ожидаемый результат. Ему нет дела до внутренних деталей реализации.

Некоторые команды также запускают на этом этапе контрактные тесты. Если изменение затрагивает интерфейс или контракт API, его ранняя проверка предотвращает сюрпризы на следующих этапах. Контрактные тесты обычно достаточно быстры, чтобы выполняться вместе с юнит-тестами.

Что здесь запускать не стоит: интеграционные тесты, end-to-end тесты и любые тесты, требующие базы данных, внешнего сервиса или полного окружения. Их место — позже.

Этап сборки: интеграционные тесты и снова контрактные тесты

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

Обычно такие тесты используют тестовые дублеры или легковесные контейнеры, а не полноценные стейджинг-окружения. Они проверяют, что «проводка» правильная: приложение может открыть соединение, отправить запрос и обработать ответ.

Контрактные тесты часто запускаются снова на этом этапе, но уже против собранного артефакта. Это гарантирует, что скомпилированный код все еще удовлетворяет контракту, а не только исходный код.

Этап стейджинга: End-to-End тесты и Smoke-тесты

Стейдж должен максимально точно повторять продакшен. Здесь запускаются end-to-end тесты — но только для критических пользовательских сценариев. Не каждая страница, не каждая функция. Только самые важные потоки: вход в систему, оформление заказа, поиск или любая другая ключевая бизнес-логика.

End-to-end тесты медленные и дорогие. Запускать их для каждого мелкого изменения — пустая трата времени. Приберегите их для изменений, затрагивающих критические пути или добавляющих новую функциональность.

Smoke-тесты также выполняются здесь. Smoke-тест — это быстрая проверка того, что приложение корректно отвечает после развертывания. Если smoke-тест падает на стейджинге, не переходите к продакшену. Остановите пайплайн и разберитесь.

Этап продакшена: Smoke-тесты и синтетические транзакции

В продакшене тесты должны быть минимальными и не влиять на пользователей. Запустите smoke-тест сразу после развертывания, чтобы убедиться, что основные эндпоинты возвращают правильные коды ответов. Это не глубокая проверка — просто проверка работоспособности, что развертывание не сломало приложение.

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

Чего делать не стоит

Не запускайте все тесты на всех этапах. Это самая распространенная ошибка. Запускать end-to-end тесты на этапе коммита бессмысленно — окружение не готово, а обратная связь слишком медленная. Повторно запускать юнит-тесты на этапе продакшена — пустая трата ресурсов: логика не изменилась между стейджингом и продакшеном.

Хорошее практическое правило: если тест прошел на более раннем этапе, не повторяйте его на более позднем, если только изменение окружения не может повлиять на результат. Юнит-тесты не зависят от окружения, поэтому их не нужно запускать снова. Smoke-тесты зависят от развертывания, поэтому они должны выполняться в каждом новом окружении.

Риск-ориентированное тестирование в пайплайне

Размещение тестов также зависит от риска. Изменение в платежной системе или логике аутентификации требует больше уровней тестирования. Изменение цвета кнопки или опечатка в надписи требует только юнит-тестов и smoke-теста.

Это риск-ориентированное тестирование, примененное к пайплайну: чем выше риск изменения, тем больше уровней тестирования оно должно пройти. Чем ниже риск, тем быстрее оно может двигаться по пайплайну.

Некоторые команды реализуют это с помощью тегов изменений или политик веток. Критические пути требуют end-to-end тестов на стейджинге. Некритические пути пропускают их. Это сохраняет пайплайн быстрым для низкорисковых изменений, обеспечивая безопасность для высокорисковых.

Практический чек-лист по размещению тестов

  • Юнит-тесты запускаются на этапе коммита. Быстро, без зависимостей, мгновенная обратная связь.
  • Контрактные тесты запускаются на этапах коммита и сборки. Проверяют интерфейсы рано и против артефакта.
  • Интеграционные тесты запускаются на этапе сборки. Проверяют соединения с зависимостями через контейнеры или тестовые дублеры.
  • End-to-end тесты запускаются только на этапе стейджинга. Покрывают критические пользовательские сценарии, а не каждую функцию.
  • Smoke-тесты запускаются на этапах стейджинга и продакшена. Быстрые проверки работоспособности после каждого развертывания.
  • Синтетические транзакции запускаются периодически в продакшене. Выявляют регрессии со временем.
  • Не повторяйте тесты на разных этапах, если только изменение окружения не имеет значения.
  • Адаптируйте уровни тестирования в зависимости от риска. Высокорисковые изменения получают больше покрытия.

Вывод

Правильно размещенный тест дает быструю обратную связь, когда она нужна, и тщательную проверку, когда вы можете себе это позволить. Цель не в том, чтобы тестировать всё везде. Цель — поймать нужную ошибку в нужное время, чтобы ваша команда могла исправить ее до того, как она попадет в продакшен.