Как написать автотесты деплоя и сэкономить нервы DevOps-инженеров |
28.03.2023 00:00 |
Привет! Меня зовут Артём Комаренко, я работаю на позиции QA Lead в команде PaaS в СберМаркете. Хочу поделиться историей, как мы придумывали способ быстро убедиться, что очередные изменения в скриптах деплоя не разломают процесс выкатки во всей компании. Статья будет полезна QA-специалистам и DevOps-инженерам, которые хотят автоматизировать тесты инфраструктуры. Вы узнаете как и с помощью чего можно проверить такую сущность как деплой. В статье я буду рассказывать о примерном ходе работы, опуская специфику конкретно нашей компании. Фрагменты кода также будут отражать только идею, без промежуточных переменных, с упрощенными наименованиями, без точного количество аргументов и т.п. <a name="why">Зачем всё это?</a>В Сбермаркете мы разрабатываем PaaS, и одной из важных частей нашей платформы является CI/CD pipeline. То есть наши пользователи-разработчики получают «из коробки» готовый pipeline, в котором уже есть различные задачи по запуску тестов, линтеров и прочих ништяков, а также задачи по выкатке приложения на тестовые стенды и созданию релиза для выкатки на прод. И вот однажды ко мне пришел лид команды DevOps с запросом «Хочу автотесты!» <a name="plan">План действий</a>Мы определили, что для PaaS важно убедиться, что разработчик, который первый раз воспользовался нашими инструментами, сможет без проблем выкатить свежесозданный сервис. А для DevOps инженеров было важно знать, что после внесения изменений в скрипты всё ещё можно спокойно деплоить новую версию приложения поверх существующей. Таким образом определился базовый набор сценариев:
Сценарии отличаются началом, где мы получаем сервис, и списком проверок. И в общем виде выглядят так:
Три кита, на которых держится автотест:
С вводными определились, можно приступать к написанию теста. <a name="script">Скриптуем в лоб</a>Для начала мне нужно было выстроить логику теста, понять как взаимодействовать с сущностями. Поэтому я решил написать тест как обычный скрипт. Этот этап не требуется повторять, можно сразу писать по правилам тестового фреймворка. Основные действия я вынес в методы хелперов:
Для красивых проверок я использовал библиотеку assertpy.
Скрипт сработал. И тут же я столкнулся с первой трудностью – следующий прогон падал, потому что состояние тестового репозитория изменилось. Нужно за собой прибраться: дописываем в конце нашего скрипта инструкции по откату изменений в репозитории, закрытию МРа, удалению ветки, чистке неймспейса.
Теперь можно запускать прогоны один за другим, все работает. Но как только что-то пошло не так, тест упал — чистка не произошла. Пришло время использовать привычный тестовый фреймворк – pytest. <a name="test">Тест здорового человека</a>Основным вызовом при переходе к pytest стало понять что и как перенести в фикстуры, чтобы в тесте осталась только логика самого теста. В фикстуры переехал функционал по подготовке локальной среды к тесту, инициализации хелперов, клонированию репозитория, и самое важное — чистка по завершению. Как итог, наш скрипт преобразился в стандартный тест:
За счет очистки в финализаторах фикстур, тест стал проходить даже если в середине произойдет сбой. А также теперь можно создавать и запускать любое количество тестов, а не по одному, как было раньше. <a name="cicd">Перемещаемся в пайплайн</a>При запуске локально тесты работают. Но нам нужно переместить их в пайплайны. Сразу перейду к списку того, что потребовалось тюнить:
<a name="manydevs">Раз разработчик, два разработчик…</a>Следующим вызовом стала проблема параллельного запуска тестов. Когда я их писал, то и запускал только я один. Но теперь их используют несколько разработчиков. Прогоны начали мешать друг другу, скапливаться в очереди, потому что тестовый репозиторий всего один. Нужно создать больше тестовых репозиториев. Но как тест узнает, какой репозиторий использовать? В рантайме эту информацию не удержать, случайный выбор не даёт гарантий. Я решил создать отдельный сервис, который заберет на себя эту работу. <a name="box">Коробка с болванчиками</a>Я использовал наш PaaS и написал небольшой сервис на Golang. Список тестовых репозиториев и их статус хранятся в Postgres. Сервис предоставляет три gRPC-ручки:
Также рядом с сервисом есть кронджоба, которая разблокирует сервисы, которые заблокировали и забыли. Блокировку и разблокировку тестового сервиса я вынес в фикстуру:
И теперь каждый тест деплоя проходит на отдельном сервисе, их состояние изолированно друг от друга. Плюс у инженеров есть время отладить тестовый сервис, если тест найдет ошибку, потому что вперед выделяются более старые сервисы. <a name="future">Развитие</a>На двух простых тестах мы не остановились. Уже сделано:
Сейчас основные направления для дальнейшего развития:
<a name="problems">Проблемы</a>Разумеется, трудности встречались регулярно и до конца никогда не исчезнут. Инфраструктура может сбоить, что приводит к падению теста. Вот примеры некоторых проблем:
Для минимизации этих проблем мы итеративно улучшаем наши тесты: добавляем явные ожидания, retry-механизмы для нестабильных запросов, переделываем способы запуска тестов из пайплайна. <a name="overall">Резюме или А что по цифрам?</a>Основной набор состоит из 5 e2e-тестов. При нормальных условиях тест проходит за ~15 минут. Бегают они параллельно. Тестовый набор запускается минимум 1 раз на МР и в среднем 10-15 раз в день, в зависимости от нагрузки инженерной команды. В месяц выходит порядка 250 запусков тестового набора. Выполнение этих же операций вручную занимает в разы больше времени и представляет собой не самую интересную часть работы. Автотесты позволяют нам находить ошибки на ранних этапах, экономят время и никогда не устают. Если вы хотите попробовать такие тесты у себя, то чтобы превратить их в рабочий код нужно:
Буду рад, если наши наработки вам пригодятся. Если вы писали автотесты для деплоя как-то по-другому, то интересно будет услышать, в чём мы разошлись! |