Почему ваш пайплайн нуждается в тестах и сканировании до того, как станет слишком поздно
Вы только что закончили сборку приложения. Сборка прошла успешно. Артефакт существует. Что дальше?
Многие команды останавливаются на этом. Они считают, что если код компилируется и сборка проходит, артефакт готов к продакшену. Но успешная сборка говорит лишь о том, что код можно собрать. Она не говорит о том, работает ли код на самом деле, есть ли в нём уязвимости или упадёт ли он при обращении к базе данных.
Пропускать проверки на этом этапе — всё равно что отправлять посылку, не заглянув внутрь. Вы можете отправить что-то сломанное, опасное или и то, и другое.
Начните с самой быстрой обратной связи: модульные тесты
Первая проверка в любом пайплайне — модульные тесты. Эти тесты проверяют поведение вашего кода изнутри. Вы вызываете функцию, вариант использования или эндпоинт и проверяете, совпадает ли результат с ожидаемым.
Следующая блок-схема показывает порядок проверок и критические точки принятия решений, где сбой останавливает пайплайн:
Модульные тесты выполняют ваши фактические слои логики — от точки входа до самого глубокого модуля. Им не нужна реальная база данных, внешние сервисы или сетевые вызовы. Именно это делает их быстрыми. Хороший набор модульных тестов выполняется за секунды или, в крайнем случае, за несколько минут.
Вот минимальный фрагмент пайплайна YAML, который запускает модульные тесты и сканирование уязвимостей, останавливая пайплайн в случае сбоя любого из них:
jobs:
test-and-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm test
- name: Run vulnerability scan
run: npm audit --audit-level=high
Если модульный тест падает, немедленно остановите пайплайн. Нет смысла продолжать, если базовое поведение вашего кода нарушено. Всё остальное зависит от предположения, что код делает то, что должен. Если это предположение неверно, каждая последующая проверка — пустая трата усилий.
Проверьте, подходят ли детали друг к другу: интеграционные тесты
Модульные тесты доказывают, что осмысленное поведение работает, когда внешний мир контролируется. Ваши внутренние слои могут работать вместе, но соседние системы симулируются или находятся под тестовым контролем. Программное обеспечение также ломается, когда ему приходится взаимодействовать с реальными зависимостями. Вот тут и нужны интеграционные тесты.
Интеграционные тесты проверяют, могут ли ваши модули корректно взаимодействовать. Может ли модуль пользователя сохранить данные в базу? Корректно ли отвечает API, когда платёжный сервис недоступен? Совпадают ли форматы данных между сервисами?
Эти тесты медленнее модульных, потому что им нужна реальная инфраструктура: база данных, очередь сообщений или тестовый экземпляр другого сервиса. Но именно здесь скрывается большинство реальных багов. Код, проходящий все модульные тесты, всё равно может провалить интеграционные из-за:
- Неправильных строк подключения к базе данных
- Несовместимых схем данных
- Неверных значений конфигурации
- Отсутствующих переменных окружения
Интеграционные тесты ловят те проблемы, которые проявляются только при реальном взаимодействии компонентов. Если их пропустить, вы делаете ставку на то, что ваш код будет идеально работать в среде, которую вы не тестировали.
Сканируйте сам код: статический анализ
Функциональные тесты проверяют, что делает код. Статический анализ проверяет, как написан код. Он читает ваш исходный код без его выполнения и ищет проблемные паттерны.
Инструменты статического анализа могут обнаружить:
- Переменные, которые объявлены, но никогда не используются
- Слишком сложный или глубоко вложенный код
- Потенциальные разыменования нулевых указателей
- Нарушения стандартов кодирования, согласованных вашей командой
- Паттерны, связанные с безопасностью, например, жёстко закодированные учётные данные
Статический анализ не ловит логические ошибки. Но он ловит те ошибки, которые разработчики совершают десятки раз в день и часто пропускают при ревью кода. Он также обеспечивает согласованность в команде. Когда каждый коммит проверяется по одним и тем же правилам, кодовая база остаётся поддерживаемой, даже когда команда растёт.
Найдите скрытые опасности: сканирование уязвимостей
Большинство уязвимостей безопасности в современных приложениях исходят не от написанного вами кода. Они исходят от библиотек и пакетов, от которых вы зависите. Одна единственная устаревшая зависимость с известной эксплойтом может скомпрометировать всё ваше приложение.
Сканирование уязвимостей проверяет ваш список зависимостей по базам данных известных проблем безопасности. Если в библиотеке есть критическая уязвимость, сканер отмечает её, и пайплайн должен остановиться. Лучше задержать релиз, чем отправить в продакшен известную дыру в безопасности.
Это сканирование должно запускаться при каждой сборке, а не только перед крупными релизами. Новые уязвимости обнаруживаются каждый день. Библиотека, которая была безопасна на прошлой неделе, сегодня может иметь критический CVE. Регулярное сканирование гарантирует, что вы поймаете эти проблемы до того, как они достигнут пользователей.
Сохраняйте доказательства: почему результаты нужно сохранять
Каждая проверка на этом этапе даёт результаты. Модульные тесты проходят или падают. Интеграционные тесты сообщают, какие сценарии сработали. Статический анализ выводит список предупреждений и ошибок. Сканирование уязвимостей помечает зависимости.
Эти результаты — доказательства. Они подтверждают, что пайплайн выполнил свои проверки, и показывают, что произошло. Сохраняйте их.
Доказательства важны по трём причинам:
Отладка проблем в продакшене. Когда что-то идёт не так в продакшене, вы можете проверить, обнаружил ли пайплайн проблему. Если да, вы знаете, что проверка сработала. Если нет, вы знаете, что нужен лучший тест.
Аудит и соответствие требованиям. Регуляторы, клиенты и внутренние политики часто требуют доказательств того, что каждое изменение было протестировано перед релизом. Сохранённые доказательства удовлетворяют этому требованию.
Анализ тенденций. Со временем доказательства показывают, улучшается ли качество вашего кода. Становятся ли тесты менее уязвимыми? Уменьшается ли количество уязвимостей? Являются ли определённые модули постоянно проблемными? Эти данные помогают решить, куда направить усилия по улучшению.
Храните доказательства в формате, который могут читать машины, например, JUnit XML или SARIF, а также сохраняйте человекочитаемую сводку. Помещайте их в место, которое сохраняется после завершения пайплайна, например, в реестр артефактов или выделенное хранилище. Не полагайтесь на логи пайплайна, которые очищаются через несколько дней.
Практический чек-лист для этого этапа
Прежде чем перемещать артефакт в развёртывание, убедитесь, что эти проверки на месте:
- Модульные тесты запускаются при каждом коммите и останавливают пайплайн в случае сбоя
- Интеграционные тесты покрывают критически важные взаимодействия компонентов
- Статический анализ обеспечивает соблюдение стандартов качества кода
- Сканирование уязвимостей проверяет все зависимости
- Все результаты сохраняются как доказательства с метками времени и идентификаторами коммитов
Что происходит дальше
После того как тесты и сканирование пройдены, вы знаете, что артефакт стоит сохранить. Он работает корректно, код поддерживаем, а зависимости безопасны. Теперь нужно упаковать его и сохранить, чтобы можно было развернуть позже.
Но если какая-либо проверка не удалась, пайплайн останавливается. Команда получает уведомление. Артефакт никогда не достигает следующего этапа. В этом и смысл: поймать проблему здесь, а не в продакшене.
Стоимость обнаружения бага в продакшене экспоненциально выше, чем его обнаружения в пайплайне. Неудачный тест стоит нескольких минут времени разработчика. Инцидент в продакшене стоит доверия пользователей, реагирования на инциденты и ночных сессий отладки.
Тестируйте и сканируйте рано. Тестируйте и сканируйте автоматически. И сохраняйте доказательства. Ваше будущее «я» скажет вам спасибо, когда что-то пойдёт не так, и вы сможете точно доказать, что и когда было проверено.