Подход к автоматизации тестирования CI

b20a703c1c41c473c187bb573dcc9f92.jpg

Всем привет! Я Лена, инженер по автоматизации тестирования в Т-Банке. Поделюсь нашим опытом в разработке подхода к тестированию, который мы применили к одной особенной сущности — к CI. Расскажу, какие техники используем и как наладили процесс тестирования новых изменений.

Выбираем, что тестировать

В нашем отделе занимаются разработкой неавторизованной зоны сайта Т-Банка, которая предоставляет функциональности для приобретения карт, оформления страхования, мобайла и многого другого. 

Неавторизованная зона сайта — это набор независимых приложений для бизнес-продуктов. Каждое приложение находится в отдельном репозитории и имеет собственный релизный цикл, независимый от других приложений. Все приложения типовые: они базируются на едином git flow и проходят одинаковый набор шагов в процессе CI/CD. 

Для настройки процессов CI/CD мы используем GitLab. Учитывая то, что большая часть YAML-кода для CI/CD наших приложений схожа, мы решили переиспользовать код. Для удобства построения CI мы вынесли общие файлы конфигурации в отдельный репозиторий и импортируем их в конечные приложения. 

Например, вот так в общем репозитории выглядит файл dynamic-stands.yml, в котором хранится джоба деплоя приложения на динамический стенд:  

Dynamic_deploy:
  stage: stand_generate_specs
  allow_failure: true
  extends:
    - .unic_common
  needs:
    - job: build_app
      artifacts: true
    - build_docker_dynamic
  before_script:
    - *check_branch_name_length
    - check_branch_name_length
  script:
    - unic deploy --app=$APP_ID --stand=$UNIC_STAND --dockerImage=$DOCKER_IMAGE
  environment:
    name: dyn/${CI_COMMIT_REF_SLUG}
    url: $DYNAMIC_URL
    on_stop: Delete_dynamic

В репозиторий своих приложений команды просто импортируют файл с этой джобой:

include:
  - project: ded-pwa/pwa-ci
    file:
       - /gitlab-ci/dynamic-stands.yml

У нашего подхода к управлению общими файлами конфигурации есть несколько преимуществ:

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

  • Стандартизация: использование общих файлов конфигурации фиксирует и поддерживает общие стандарты во всех наших приложениях. 

  • Улучшение эффективности работы: переиспользование сокращает время, затрачиваемое на настройку и управление процессом CI/CD на стороне команд приложений.

    У этих плюсов есть обратная сторона: неудачные изменения сломают CI/CD сразу всем командам, использующим файлы из общего репозитория. Например, разработчик внес изменение в общий CI-репозиторий, оно автоматически применилось к CI/CD всех приложений. Команды в своих репозиториях хотят запустить CI/CD и узнают о том, что CI сломан. В итоге процесс релиза откладывается, а разработчик экстренно делает фикс в общем репозитории с CI. 

Столкнувшись на практике с такими инцидентами, мы поняли, что изменения в CI нужно тестировать. Перед написанием тест-кейсов и автоматизацией нужно определить, что тестировать, — это и стало первой сложностью.

В нашем общем репозитории мы храним множество различных сущностей: готовые джобы GitLab, bash-скрипты для выполнения общих действий, docker-образы, необходимые для автотестов. Некоторые из этих сущностей могут быть связаны друг с другом: например, джобы могут использовать скрипты, наследоваться друг от друга или требовать наличия других джоб в своих зависимостях.

Начать я решила с написания тест-плана.

Исходим из кейсов использования

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

Пример схемы пайплайна с указанием джоб и связей между ними

Пример схемы пайплайна с указанием джоб и связей между ними

С помощью схем пайплайнов мне удалось понять, что из репозитория для хранения CI используется в реальных приложениях, а что нет. И сразу отсеять тест-кейсы с устаревшей функциональностью. А еще я проанализировала функции каждой джобы и ее связь с другими джобами. На основе выполняемой функциональности определила критичность ее проверки. 

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

В итоге у меня появился набор тест-кейсов, которые стоит проверять при внесении изменений в эту репу. Их оказалось достаточно много — около 50, вот часть из них.

f778f536b1f47b767af58f78c7ba6e98.png

Следующий шаг — сократить количество проверок.

Используем техники тест-дизайна 

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

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

Схема построена с помощью mermaid. Зеленым выделены шаблоны джоб, сами они не исполняются, но используются для наследования в других джобах. Красными выделены исполняемые джобы — в красном квадрате, наиболее приоритетные, использующиеся в наших продуктовых репозиториях

Схема построена с помощью mermaid. Зеленым выделены шаблоны джоб, сами они не исполняются, но используются для наследования в других джобах. Красными выделены исполняемые джобы — в красном квадрате, наиболее приоритетные, использующиеся в наших продуктовых репозиториях

Анализ структуры помогает:  

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

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

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

Остановимся на третьем пункте. Например, есть набор джоб, предназначенных для одной и той же цели — выгрузки файлов исходников приложения в облачное хранилище. Почему тогда это разные джобы? Они запускаются в разных случаях: одна — в MR, другая — на ветке master, а третья — на ветке prod. Не зная внутренней структуры, я бы, скорее всего, пришла к выводу, что нужно тестировать все джобы. Но диаграмма подсказывает: все джобы наследуются от одного шаблона. В нем указана вся основная логика, и различаются джобы только правилами запуска. 

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

Исходим из проблем

Если полученное мной покрытие полное, составленные тест-кейсы должны отловить проблемы, которые были внесены в репу для хранения CI и сломали CI приложений. 

У нас в команде принято заводить в Jira все баги, даже если это не дошедшие до пользователя проблемы, а наши внутренние поломки инструментов. Проанализировав баги в репе для хранения CI за последние полгода, я нашла еще несколько кейсов, которые стоит проверять:  

  • Синтаксис всех YML-файлов. Я решила не тестировать все джобы, потому что полное тестирование избыточно. Но периодически возникает кейс, когда ошибка в синтаксисе YML-файла ломает CI, потому что GitLab не может собрать пайплайн из-за нее. Поэтому синтаксис проверять нужно для всех поставляемых YML-файлов.

  • Синтаксис всех bash-скриптов. Аналогичная ситуация: некорректный синтаксис может сломать джобы, которые было решено не тестировать.

  • Логика функций. Bash-скрипты в репе достаточно сложные и порой используют внутри JS-функции. Логику функций можно покрыть юнитами, чтобы, запуская джобы, не тестировать правильность написанных функций.

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

Примеры из набора тест-кейсов

Примеры из набора тест-кейсов

Настраиваем процесс тестирования 

Тест-план готов, осталось наладить процесс тестирования всех новых изменений. Часть проверок можно выполнить вручную: в GitLab есть механизм указания ссылок на ветку, из которой подключается джоба. По умолчанию это master, но можно указать другую. Выглядит это так:

include:
  - project: ded-pwa/pwa-ci
    file:
       - /gitlab-ci/dynamic-stands.yml
    file: branch-name

Мы можем подтянуть изменения из нашей ветки в репе с общим CI в репозиторий какого-то из наших приложений и убедиться, что CI/CD приложения не сломался. Есть минусы:

  • Очень долго. Ручное подключение новой функциональности и ручная проверка работоспособности CI/CD занимают очень много времени тестировщиков.

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

Так что не будем останавливаться на достигнутом и перейдем к способам автоматизации

Используем статические анализаторы кода

Для части проверок автоматизация может быть реализована с помощью статических анализаторов кода или стандартных методов проверки кода. Делать это можно прямо в репозитории с общим CI:

  • Проверка синтаксиса YML-файлов — критичный для нас кейс. Можно проверять с помощью библиотеки yamllint.

  • Синтаксис bash-скриптов можно проверять с помощью shellcheck. 

  • Логика JS-функций. Можно проверять с помощью юнитов с использованием всем известного Jest.

Создаем песочницу для проверки YML-файлов

С проверкой логики работы и корректности связи YML-файлов все не так просто. Нам нужна песочница, где мы сможем тестировать и проверять версии наших YML-файлов, используемых для CI/CD. Это позволит нам убедиться, что новые версии файлов работают как надо, прежде чем их разворачивать на реальные приложения. Для этого мы создали отдельный репозиторий с простым тестовым приложением, которое будет служить платформой для запуска наших YML-файлов. 

В песочнице мы собрали приложение, похожее на реальное, и настроили там CI, аналогичный CI наших реальных приложений. Дальше нужно было настроить окружение, которое позволит тестировать новые версии наших файлов из общего репозитория. Для этого понадобилось несколько фич GitLab:

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

  • Использование Multi-project pipelines — они позволяют из репозитория с общими YAML-файлами настроить запуск джоб в репозитории с тестовым приложением. В коде триггера указан репозиторий, в котором будут запущены джобы. Стратегия depend позволяет в исходном пайплайне дождаться окончания выполнения запущенных по триггеру джоб, чтобы нельзя было замержить пайплайн до окончания всех проверок. 

test-app-simulation:
  stage: test_integration
  except:
    - tags
    - master
  variables:
    APPS_CI_REF: $CI_COMMIT_REF_NAME
    APPS_CI_SLUG: $CI_COMMIT_REF_SLUG
  trigger:
    strategy: depend
    branch: master
    project: ded-pwa/pwa-ci-test-app

Мы настроили окружение, в которое подключаем новую версию YML-файлов, имитируем аналогичные реальным приложениям условия запуска: например, merge request, master-ветку и prod-ветку. И после этого проверяем, что все пайплайны проходят успешно. 

Проверка настроена автоматически: мы подключаем тестовый пайплайн в репозиторий с нашим общим кодом с помощью механизма GitLab-триггеров. Подробнее о том, как работают триггеры в GitLab:

Наш процесс тестирования CI выглядит так:

  1. Разработчик хочет внести изменения в общий репозиторий CI/CD и создает новую ветку с изменениями.

  2. В ветке запускаются статические проверки из предыдущего шага. 

  3. В ветке запускается джоба-триггер, который запускает пайплайн в тестовом репозитории, импортируя в него YML-файлы новой версии.  

  4. Джоба ждет завершения пайплайна в тестовом репозитории, если в нем есть ошибки, пайплайн падает и сигнализируют разработчику о необходимости фикса. Успешно пройденный пайплайн гарантирует нам, что новые изменения безопасны и мы можем их раскатывать на все наши приложения.

_____________________

Теперь мы начали относиться к нашему CI как к продукту, который является важной частью процесса разработки, и поняли, что его тоже надо тестировать. В итоге работы мы получили тест-план тестирования общего репозитория для организации CI/CD.  

Мы автоматизировали проверки, используя статические инструменты, и создали тестовую песочницу для проверки YML-файлов. Это сократило количество инцидентов, связанных с блокировкой процесса CI/CD, и позволило наладить процессы обеспечения качества в одном из наших критичных инфраструктурных репозиториев.

Поделитесь, если у вас был похожий опыт или остались какие-то вопросы :) 

© Habrahabr.ru