В итоге, можно утверждать, что написанный проект отлично подходит для интеграции с разработанной системой тестирования в общем и современной JavaScript-экосистемой, в частности. Дальнейшее использование, выкладка и тестирование приложения, связанное с такими инструментами, как: TravisCI, WebdriverIO, Selenium Server, Docker и Kubernetes - происходит благодаря работе с написанным веб-приложением, собранным с помощью команд npm (Node Package Manager) и по указанной конфигурации для Webpack. О том, как решались следующие задачи, будет рассказано далее.
9.2 Разработка алгоритма непрерывного тестирования кода
Когда речь заходит о непрерывном тестировании, под этим подразумевается несколько комплексных аспектов:
Контейнеризация;
Оркестрация;
E2E-тестирование.
В описании решения данной задачи будет рассказано о первых двух пунктах. О последнем же речь пойдет в части о Selenium Server и WebdriverIO.
9.2.1 Контейнеризация и Docker
Контейнеры стали стандартами в IT-области, особенно когда речь заходит о работе с развертыванием микросервисов. Основная причина их популярности кроется в том, что, используя контейнеры, не обязательно более «поднимать» отдельные виртуальные машины для развертывания сервисов на машинах. Тех же результатов можно достичь, используя меньше ресурсов. Docker [21] - это одно из самых используемых решений на рынке контейнерной виртуализации.
Контейнеры строятся на базе всей операционный системы. Это означает, что они намного более эффективны, чем, например, гипервизоры [22], используемые в виртуальных машинах. Контейнеры располагаются внутри одной Linux-машины и составляют небольшую аккуратную «капсулу» с приложением или сервисом, который необходимо развернуть или использовать. Поэтому с идеально настроенной контейнерной системой можно в разы увеличить количество экземпляров приложений на одной машине и существенно оптимизировать нагрузки.
Другая причина популярности контейнеров заключается в том, что они отлично подходят для развертывания и тестирования, то есть построения CI/CD-систем. Они позволяют разработчикам чаще обновлять код проекта, а затем быстро и эффективно развертывать код.
Docker позволяет разработчикам быстро упаковывать и запускать любое приложение в виде легкого, портативного, самодостаточного контейнера, который может работать практически в любом месте. Таким образом, контейнеры обеспечивают мгновенную переносимость приложения.
Контейнеры достигают этого путем изоляции кода внутри себя. Это облегчает модификацию и обновление программы. Таким образом, контейнеры позволяют компаниям разбиваться внутри себя на небольшие группы, использующие системы, подобные TravisCI, для автоматизации доставки нового программного обеспечения в контейнерах. Это позволяет организациям делать приложения и их развертывание более мобильными, эффективным, распределенным и стандартизированным.
Кроме того, Docker-контейнеры гораздо легче развертывать в облаке или удаленных машинах, ведь его можно использовать отдельно для управления средами разработки.
В частности, для CI / CD Docker позволяет настраивать локальные среды разработки, которые точно соответствуют обычному серверу; запускать несколько сред разработки на одном хосте с уникальным программным обеспечением, операционными системами и конфигурациями; тестовые проекты на новых или разных серверах; и позволять любому работать над одним и тем же проектом с одинаковыми настройками независимо от среды локального хоста. Это позволяет разработчикам запускать наборы тестов, которые жизненно важны для CI / CD, чтобы быстро увидеть, работают ли вновь сделанные изменения правильно.
Интегрируя CI/CD-систему вместе с Docker, IT-компании с настроенным рабочим процессом выкладывают обновления чаще. Более того, в случае падения систем, они восстанавливаются гораздо быстрее, ведь образ контейнера с предыдущей версией проекта остается на Linux-машине.
На практике нашей системы получается, что frontend-часть приложения имеет в корне проектного репозитория конфигурационные скрипт-файл.
Dockerfile, которые построен на базе alpine, легковесного дистрибутива Linux, и NodeJS, который отвечает за сборку приложения.
Рис. 6. Код скрипта сборки проекта
Также с помощью скрипта в файле docker-compose.yaml указываются порты и зависимости, необходимые для корректного запуска системы.
Рис. 7. Указание портов и зависимостей на базе Dockerfile
Помимо конфигурирования образов для сборки проекта, Docker принимает участие и в процессе тестирования, ведь именно в Docker-контейнере подгружаются браузеры и запускается Selenium Server, в котором и проверяется бизнес-логика приложения.
Рис. 8. Dockerfile для запуска Selenium Server
Далее, когда происходит выкладка приложения или когда запуск Selenium Server`ов, с помощью командной строки происходит сначала сборка контейнеров и их запуск в системе оркестрации Kubernetes, о котором пойдет речь дальше.
9.2.2 Оркестрация и Kubernetes
Kubernetes [23] -- это популярная платформа с открытым исходным кодом для оркестрации контейнеров, то есть для управления приложениями, запущенными в контейнерах. Его существование обосновывается тем, что при использовании большого количества контейнеров появляется необходимость в инструменте, который автоматизирует развертывание, управление, масштабирование, создание сетей и доступность сервисов в контейнерах.
Kubernetes стал одним из самых популярных инструментов оркестрации. На практике Kubernetes чаще всего используется с Docker, самой популярной платформой контейнеризации. Однако он также может работать с любой системой контейнеров, которая соответствует стандартам Open Container Initiative (OCI). И поскольку Kubernetes является open-source проектом, то его можно свободно использовать как локально, так и на удаленной машине.
На самом деле, Kubernetes не заменяет Docker, а дополняет его. Тем не менее, Kubernetes действительно заменяет некоторые технологии более высокого уровня, которые появились вместе Docker. Kubernetes значительно сложнее, чем Docker Swarm (другое решение для оркестрации), и требует больше работы для развертывания. Однако в долгосрочной перспективе система предлагает больший функционал и устойчивость. И так как Kubernetes является лидером рынка во всех более-менее сложных IT-компаниях, то необходимость использования Kubernetes в нашем проекте очевидна.
Сама архитектура Kubernetes использует различные концепции и абстракции. Некоторые из них являются вариациями существующих, знакомых понятий из Docker, но другие характерны только для Kubernetes.
9.2.2.1 Kubernetes кластеры
Кластер в Kubernetes относится к группе машин, на которых запущен Kubernetes и контейнерам, которыми он управляет. Кластер Kubernetes должен иметь так называемого мастера, систему, которая управляет и контролирует все другие машины Kubernetes в кластере.
9.2.2.2 Kubernetes Nodes и Pods
Каждый кластер содержит узлы (англ. Nodes). Узлами могут быть физические машины или виртуальные машины. Опять же, идея заключается в абстракции: независимо от того, на каком приложении выполняется приложение, Kubernetes все равно способен обеспечить управление. Kubernetes даже позволяет гарантировать, что определенные контейнеры работают только на виртуальных машинах или только на голом железе. Например, для отладки работы сервиса через Docker и Kubernetes использовался локальный кластер minikube, запущенный в виртуальной машине на рабочем компьютере.
Узлы запускают поды (англ. Pods), самые простые объекты в Kubernetes, которые можно создавать или управлять ими. Каждый под представляет из себя сервис и состоит из одного или нескольких контейнеров. Kubernetes запускает, останавливает и регулирует количество всех подов.
Поды создаются и уничтожаются на узлах по мере необходимости, чтобы соответствовать желаемому состоянию, указанному пользователем. Иными словами, разработчики могут перераспределять мощности системы между сервисами, путем указания количества подов на каждый сервис. Kubernetes, по факту, предоставляет интерфейсы для управления логикой того, как поды должны изменяться в количестве («скейлиться»), включаться, выключаться и переезжать с ноды на ноду.
Рис.9. Скрипт создания пода
Изначально была предложена идея создать поды отдельно (см. рис. 9), загружая в них сконфигурированная Docker-образы c Selenium Server`ами или приложениями как таковыми, однако это было бы неэффективным использование имеющегося функционала. Поэтому было принято решение использовать Kubernetes-сервисы для автоматизированного управления контейнерами.
9.2.2.3 Kubernetes Services
Поскольку поды то живут, то умирают по различным причинам, появилась необходимость в управлении жизненным циклом запущенных программ. Для этих задач Kubernetes предоставляет абстракцию, называемую сервис (service).
Сервисы в Kubernetes регулируют, как поды могут изменяться, включаться, перезагружаться и переезжать с ноды на ноду. К счастью, сервисы делают это возможным. Например, типичным кейсом в нашем проекте являлось обеспечения доступа к Selenium Servers, запущенных в подах в Kubernetes. Нам было необходимо «выставить» IP-адреса подов на машине, на которой они были развернуты, чтобы система тестирования, при отправке запросов через Webdriver Protocol, могла до них достучаться. Это было реализовано с помощью сервисов NodePort и Deployment, открывающих порты для конкретного пода, указывающий количество CPU и ОЗУ для каждого пода в кластере и регулирующее количеством подов.
Рис. 10. Сервис для управления репликами и нагрузкой подов
9.2.2.5 Итог по Kubernetes
Важно помнить, что сам Docker не заменяет Kubernetes. Поскольку Kubernetes, вводя новые абстракции и концепции, управляет состоянием приложений, репликацией, балансировкой нагрузок и выделением аппаратных ресурсов, то одна из основных обязанностей, которую Kubernetes снимает с разработчиков, -- это работа по поддержанию работоспособности запущенных сервисов. В итоге, вместе с Docker данные инструменты позволили построить удобную и эффективную CI-системы для развертывания web-приложения и E2E-тестирования на базе Selenium Server.
9.3 Selenium Server и WebdriverIO
Selenium Server -- это бесплатный функциональный инструмент тестирования с открытым исходным кодом, используемый для тестирования веб-приложений в нескольких браузерах и на нескольких операционных системах. Используется, в первую очередь, для функционального и регрессионного тестирования.
Сам Selenium Server -- это не просто инструмент, а комбинация двух основных компонентов. Selenium следующих частей:
Selenium WebDriver Protocol;
Selenium Router.
9.3.1 Selenium WebDriver
WebDriver -- это интерфейс удаленного управления, который осуществляет контроль над user-agent [24]. Он является независимым от платформы и языка программирования протоколом для удаленного управления веб-браузером внешними программами.
WebDriver предоставляет набор интерфейсов для обнаружения и управления DOM-элементами на веб-страницах и для управления поведением user-agent`а. Он предназначен, прежде всего, для того, чтобы позволить разработчикам писать автоматизированные E2E-тесты.
Каждый браузер обладает своим собственным протоколом взаимодействия такими, как:
Chrome Driver;
Gecko Driver (Firefox);
IE Driver;
Opera Driver;
Safari Driver.
- которые нужно указывать отдельно для взаимодействия с конкретным браузером. Настройка конфигураций браузеров происходит с помощью тестового фреймворка. Так как веб-приложение существует в экосистеме языка JavaScript, то в качестве тестового фреймворка был выбран WebdriverIO [25], который является переносом классического Webdriver на NodeJS.
WebdriverIO является отличным современным инструментом для E2E-тестирования. Он позволяет:
Добавлять создавать новые, более сложные команды, а API существующих команд гораздо проще для понимания и работы;
Поддерживать Appium и позволяет запускать тесты на мобильных устройствах;
Реализовывать все команды протокола Webdriver и обеспечивает полезную интеграцию с другими инструментами тестирования (отчетами, роутерами).
9.3.2 Selenium Router
Для обеспечения параллельного запуска тестов необходим роутер Selenium запросов. Каждый метод, вызванный через WebdriverIO, по факту, отправляет HTTP-запрос к браузеру, на который он соответствующе реагирует.
Существует несколько готовых решений:
Selenoid;
Selenium Grid;
Gridrouter.
Однако все они страдают большими проблемами при высоких нагрузках: теряются сессии, не доходят запросы, перегружается система, на которой поднятые Selenium сервера, не работают в Docker и Kubernetes. По этой причине было принято написать свое собственное решение по собственной логике (см. рисунок 11).
Рис. 11. Диаграмма логики работы Selenium Router
По факту, были сконфигурированы отдельные Docker-image, содержащие в себе установленные Google Chrome и Firefox браузеры вместе с соответствующими для них Chrome Driver и Gecko Driver (см. рисунок 8). Данные Docker контейнеры были выгружены в Kubernetes кластер и их адреса на хост-машине были выставлены через NodePort для внешнего взаимодействия. Далее, данные адреса были сохранены в MongoDB, из которой каждый тест в момент инициализации получал для себя адрес со свободным Selenium Server`ом. В итоге, получались рабочая система, которая сразу показала свою жизнеспособность, а именно:
Предсказуемое поведение;
Отказоустойчивость на уровне БД;
Масштабируемость;
Портативность;
Поддержка большого количества нод.
9.4 Запуск системы
Запуск системы инициализируется обновлением кода в любой из веток в системе Git и отправке изменений на GitHub. Логика pipeline формируется скриптом в файле travis.yaml, который каждая из компаний сможет редактировать под свои нужды (см. рисунок 12).
Рис.12. Логика pipeline для запуска системы
Таким образом, формируется Docker-образ в системе TravisCI, содержащий в себе весь код проекта. Далее, запускаются тесты с помощью npm-скрипта.