Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Этой статьей мы продолжаем серию публикаций о том, как мы автоматизировали в одном из крупных проектов ЛАНИТ автопроцесс ручного тестирования (далее – автотесты) большой информационной системы (далее – Системы) и что у нас из этого вышло.
Вторая часть публикации ориентирована в первую очередь на лидеров групп автоматизации UI end-2-end тестирования и ведущих тест-автоматизаторов. Здесь они найдут конкретные рецепты по архитектурной организации кода и развертывания, которая поддерживает массо-параллельную разработку больших групп тестов в условиях постоянной изменчивости тестовых спецификаций. В этой части приведен полный состав необходимых для UI-тестов функций с некоторыми деталями реализации, а также есть перечень сюрпризов, с которыми вы можете столкнуться.
Вот здесь вы найдете Часть 1. (Зачем нам была нужна автоматизация. Организация процесса разработки и управления. Организация использования)
Источник
Основные технологии и библиотеки, используемые в проекте:
Общая схема компонентов автотестов и Selenoid-инфраструктуры показана на схемах ниже, включая объясняющие комментарии:
Autotests Framework
Приложение для автоматизации регресса пользовательского интерфейса.
Поставляется в исходном коде. Использует JUnit4
run.properties
Конфигурационный файл для запуска Autotests. Определяет условное имя используемого стенда и тип исполнения — локальный или через внешние контейнеры и другие переменные.
Allure Plugin
Специальный исполняемый файл, который устанавливается на Bamboo сервер.
Создает тестовый HTML-отчет, доступный через Bamboo-сервер.
Test Report
Тестовый HTML отчет, доступный через Bamboo сервер.
Хранится на Bamboo-сервере в результатах плана на отдельной вкладке.
Bamboo
Обеспечивает запуск интеграционного тестирования как в автоматическом, так и в ручном режиме.
Хранит отчеты о тестировании в формате Allure.
ggr-server
Сервер-балансировщик Selenoid-серверов.
Обеспечивает балансировку запросов от автотестов (RemoteWebDriver) к нескольким экземплярам серверов selenium.
Docker
Докер-сервер для запуска контейнеров серверов и браузеров Selenoid.
Selenoid-server
Сервер удаленного тестирования
Обеспечивает запуск тестов в специализированных докер контейнерах с использованием «безголового» браузера.
Выполняет тесты в параллельном режиме согласно задаваемому количеству одновременных потоков.
Selenoid-ui
Сервер пользовательского интерфейса к серверу Selenoid.
Позволяет «на лету» контролировать ход тестирования через VNC.
Selenoid-webdriver
Специализированный контейнер с безголовым браузером для исполнения удаленных тестов.
Предоставляется из репозитория Selenoid.
GitLab
Хранит исходные коды приложения Autotests.
![](https://habrastorage.org/getpro/habr/post_images/c5d/cac/f81/c5dcacf817e4aa1529d7cc4d64be740d.png)
На следующей схеме показана общая схема работы сервиса «Автотесты» на уровне High Level Design.
![](https://habrastorage.org/webt/ye/e8/ue/yee8uexl8oqy5spoqirropxglha.png)
Автотесты базируются на четырех серверах, один из которых – сервер исполнения, а остальные три обеспечивают запуск безголовых браузеров в контейнерах. Текущая мощность автотестов обеспечивает 60 одновременных потоков и при необходимости может быть расширена.
Текущая схема развертывания показана на следующей схеме. В целом, если вам не требуется более 20 одновременных браузеров (потоков тестирования), то вполне можно всё поставить на один сервер 12 ЯДРА + 24 ОЗУ. Мы начинали с этой конфигурации, но по мере роста требований к мощности автотестов мы выяснили эмпирически, что наиболее устойчивая и экономически выгодная конфигурация – это «типовой» сервер 12 ЯДРА + 24 ОЗУ.
По нашему опыту, для тестирования систем с веб-интерфейсом на Angular приемлемая конфигурация контейнера с браузером должна быть пол ядра + Гб памяти. Меньшая конфигурация замедляет работу браузера и даже может приводить к не идентифицируемым сбоям.
![](https://habrastorage.org/getpro/habr/post_images/152/65f/095/15265f09560a8abd9a744e5c06da1f61.png)
Прежде чем перейти к структуре и организации кода, ещё раз перечислю те политики и ограничения, которые в конце концов и определили направления развития архитектуры:
С учетом перечисленного, основными архитектурными драйверами стали следующие:
В целом, такой архитектурный подход позволил реализовать независимую быструю разработку в условиях высокой изменчивости сценариев с фактически непрерывным процессом поставки новых тестов в продуктив. Архитектура и сейчас успешно выдерживает «нагрузку и доработку» несмотря на то, что в системе уже реализовано более 1500 бизнес-сценариев.
Общая структура кода и описания ключевых характерных решений приведены далее.
Общая структура организации кода базируется на «слоистой» архитектуре (см. схему), которая в целом наследует Page Pattern, рекомендуемый разработчиками Selenium. Мы расширили этот паттерн, добавив уровень веб-элементов в основание и выделив уровень тест-сценария:
Каждый уровень в этой схеме имеет конкретный набор обязанностей и функциональности. Пересечение функционала между слоями или нелегитимные зависимости между ними не разрешались и были основной причиной возврата комита на доработку в рамках код-ревью перед мерж-реквестом.
Дополнительно «слоистая» структура была закреплена в делении кода по пакетам (см. схему далее).
Такая схема позволила разделить все подсистемы (которых было значительно больше, чем на схеме) между разработчиками и, как результат, дала возможность независимой разработки с практически исчезающим числом мерж-конфликтов.
Далее на схеме показана общая структура реализации тест-класса и схема распределения реализаций по пакетам проекта.
![](https://habrastorage.org/getpro/habr/post_images/c26/582/00e/c2658200ed322c07b65492aef2479074.png)
![](https://habrastorage.org/getpro/habr/post_images/caf/528/329/caf52832908c9f9d739b891d1aafb29d.png)
Уровень тест-класса включает один-единственный класс для конкретного отдельного теста (тест при этом описывается в тест-менеджмент-системе как линейная последовательность тест-сценариев). Тест-класс является Junit-классом с аннотацией @ Test соответствующего метода. В общем случае тест-класс реализует только один тестовый метод.
Каждый тест-класс наследуется от базового класса, задачей которого является инициализация всех TestRule в RuleChain, то есть инициализация тестовой инфраструктуры типа инициализации веб-драйвера, установки временных директорий и других «util handlers» общего назначения.
Тест-класс отвечает за организацию исполнения тестового сценария через:
При этом в шаге 2 должна быть линейная последовательность без каких-либо ветвлений или точек принятия решений. Шаблон реализации класса показан далее.
Уровень тест-сценария отвечает за реализацию конкретных тест-сценариев согласно описанию. Тест-сценарий в этом контексте – это последовательность операций, совершаемых пользователем над системой посредством взаимодействия с элементами на веб-страницах приложений.
Основная задача класса тестового сценария:
Класс тест-сценария организован как «функциональный интерфейc»:
Такая организация класса позволяет:
Уровень веб-страницы – это классический Page Pattern для тестирования в Selenium. Класс страницы содержит определения конкретных веб-элементов и набор публичных методов для выполнения неких групповых действий в контексте шагов тестового сценария.
Класс страницы непосредственно отвечает за ввод бизнес-данных в конкретные элементы интерфейса, работу с веб-драйвером (запуск JS, например) и, соответственно, осуществляет тестирование форматов, проверку разметки и структуры веб-страницы на предмет таких основных проверок, как: элемент не найден/не доступен и элемент не содержит требуемое значение.
Класс страницы также не включает и не может обращаться к другим страницам и их методам. Также классы страниц не осуществляют бизнес-проверки, ограничиваясь только структурными проверками на уровне веб-элементов в общем случае, предоставляя «наверх» на уровень «тест-сценария» полученные со страниц данные.
Уровень веб-элементов включает библиотеку конечных элементов, которые могут быть на веб-странице. Он включает как конкретные примитивы, так и более сложные конгломераты, состоящие из нескольких элементов, которые мы называем виджеты. Примерами виджетов могут являться такие конструкции, как «паджинаторы», глобальные меню, различные фреймы и модальные окна, сложные элементы типа YandexMap или Youtube-проигрывателя. Веб-элементы организуют композицию с конкретными классами страниц и также ничего не знают о других элементах.
В целом, если в проекте есть некая глобальная уникальная идентификация всех элементов интерфейса с их ID, то уровень веб-элементов имеет смысл организовать как глобальную библиотеку с запросом конкретных элементов по их ID через фабрику или конфигурационные классы \ xml в спринг-библиотеке. Но не в каждом проекте это возможно.
Концепция разработки автотестов, как было показано на схеме выше, базируется на идее фреймворка, при которой для всех автотестов предоставляется набор системных функций – они бесшовно интегрируются и дают возможность разработчикам автотестов концентрироваться на конкретных вопросах бизнес-реализации тест-классов.
Фреймворк включает следующие функциональные блоки:
Ключевым классом для всех тест-классов является BaseTest, от которого наследуются все тест-классы. BaseTest-класс определяет Junit «ранер» тестов и используемый RuleChain, как показано далее. Доступ из прикладных тестовых классов к функциям, предоставляемым rule-классами, осуществляется через статические методы rule-классов с использованием идентификатора потока «thread» как ключа.
Детали реализации базовых классов для фреймворка автотестов я покажу следующей части статьи: см. Часть 2-1. Реализация базового класса для всех тестов и JUnit ChainRule.
Для получения подробных отчетов по выполненным тестам используется Allure Framework, интегрированный с Bamboo через Allure Plugin. Это обеспечивает возможность конкретным потребителям тестов (команде функционального тестирования) не только получать данные о факте падения конкретного теста, но и легко восстановить и при необходимости повторить в ручном режиме упавший тест.
Для документирования отчета по тесту используется следующий функционал:
Используются следующие аннотации Allure и Junit для разметки отчета.
Наименование шагов тестирования через @ Step позволяет создать подробный отчет, который полностью повторяет все шаги и пункты, описанные в тестовом сценарии. Это позволяет пользователям автотестов легко трассировать ход тест-сценария и помогает определять точку падения.
В целом, использование Allure Framework оказалось очень полезным и легким, за исключением некоторых особенностей, связанных с огромным количеством данных, которые генерируются в нашем случае для большого количества длинных сложных тест-сценариев (описано далее в разделе «Ретроспектива»).
Несмотря на то, что при реализации автотестов, мы умышленно отказались от использования Spring Core, в общем случае я считаю его использование оправданным и рабочим. Реализованный прототип показал, использование Spring Core вполне работает для следующих задач (хотя мы конечно на полную не тестировали):
Единственной особенностью является необходимость использования «по умолчанию» контекста scope уровня prototype.
Инициализация тестовой сцены. В автотестах тестовая сцена инициализируется классическим методом создания экземпляров классов непосредственно в тест-классе через простой new или фабрики объектов. Тут вполне разумно использовать инициализацию либо через конфигурационные классы, либо через внешние xml или проперти файлы. Плюсы следующие: это 1) упрощает код ревью, так как изменения тестовой сцены более не относятся к ключевым классам, 2) позволяет подключать разные тестовые сцены для разных стендов или других условий. Сейчас пункт 2 у нас не используется.
Инициализация веб-страниц и элементов. Инжекция классов веб-страниц и веб-элементов работает в силу отложенной ленивой инициализации selenide веб-элементов.
Таким образом, появляется возможность создать библиотеку веб-элементов и страниц в соответствии с некой глобальной спецификацией пользовательского интерфейса и получать в коде ссылки на эти элементы не по абсолютному пути и id, а по проектному id согласно спецификации.
Сразу оговорюсь, что тестировал я это не очень тщательно, фактически ограничившись тестовой реализацией по этому принципу «страниц» логина и страницы «инфо» пользователя.
Здесь я описал неожиданные сюрпризы, возникшие в ходе развития проекта, плюс некоторые соображения на счет того, что можно было сделать лучше, если бы «не».
Одним из самых серьезных сюрпризов, с которым мы столкнулись, оказалась «плывущая» разметка страницы, которая приводила к тому, что после обновления Angular приложений, тесты падали, так как Selenium не мог найти элементы, потому что изменились их ИД (id, class или ng-model) или пути (для XPath). Это приводило к нестабильности тестов и неоднозначности причин падения.
К сожалению, данная проблема оказалась в целом не решаемой. Мы обходили ее организационными мероприятиями: 1) при начале тестирования нового кандидата в релиз все разработчики автотестов концентрируются на быстром устранении и доработке в части правки значений локаторов веб-элементов; 2) в конце концов мы пришли к использованию относительных XPath, которые, увы, совсем не улучшают производительность.
В идеале борьба с такими сюрпризами – в изначально встраиваемой практике уникального именования всех элементов интерфейса согласно некоему словарю, который может быть связан с глобальной доменной структурой бизнес-данных и дизайном пользовательского интерфейса на уровне всего проекта.
Для «выгрузки» файлов из тестируемой системы мы используем следующие решения:
Детали описаны в документации.
Для обоих режимов для доступа к файлу необходимо было знать имя файла. В этом и был сюрприз, ибо мы никак не могли знать, под каким именем загрузится файл на диск в контейнере. Как мы организовали загрузку файла из контейнера с браузером в автотесты, можно будет почитать в следующей части статьи.
С засорением тестовых баз данных мы столкнулись достаточно быстро. В первую очередь это было связано с тем, что для «чистоты» прохождения тестовых сценариев тестовое окружение генерировалось динамически на самом стенде. Например, для проверки функции добавления Объекта А пользователя в его личный кабинет нам последовательно надо было создать нового пользователя, фейковый договор, Объект А, разместить его по конкретному адресу, и только после выполнения всех предварительных тест-сценариев (в которых создавалась тестовая сцена) выполнить тест-сценарий связывания Объекта А с пользователем.
Легко понять, что через некоторое время по конкретному адресу, используемому для размещения объектов, появлялись тысячи и десятки тысяч этих объектов. Это начинало вызывать проблемы производительности при выполнении поисковых операций по Объектам по адресу.
Это самый яркий пример, с которым мы столкнулись. В данном случае мы просто переезжаем регулярно на «новый» адрес, и в целом процедура ротации дефолтной сцены должна проводиться регулярно, иначе возможно возникновение неприятных сюрпризов, которые выражаются в растущем времени прохождения тестов и падении тестов по таймауту.
С этой проблемой мы столкнулись очень неожиданно в ходе роста количества одновременно исполняемых тестов. Думаю, мы просто не задумывались над этим. Время прохождения полного набора тестов (~ 1000 полноценных тестов) начало приближаться к критической отметке в 6 часов.
Для увеличения количества одновременно запускаемых тестов мы перешли на Selenoid-ggr. Это позволило нам поднять количество одновременных потоков junit с 20 (один Selenoid-сервер) до 60 (три сервера), после чего мы уперлись в производительность тестового стенда. «Внезапно» оказалось, что стенд тестирования стабильно работает не более с чем 60 одновременными сессиями.
Конечно, производительность можно увеличить ресурсами, но проблема, что все эти ресурсы нужны исключительно в течение 3-4 часов, когда проводится полное ночное тестирование или периодическое preQA-тестирование. Остальное время ресурсы простаивают. Тут, конечно, сразу напрашивается AWS c почасовой арендой серверов, тем более что Selenoid-решение отлично укладывается в этот подход, но если ваш стенд не расположен одной AWS зоне с Selenoid, вы можете разориться на in/out трафике.
В любом случае, вопрос масштабирования и производительности как серверов тестирования, так и самого стенда тестирования при построении систем автотестирования с большим количеством E2E тестов следует рассматривать с начала проекта. Для этого стоит опираться на требования к максимальному времени прохождения полного регресса и preQA.
Необъяснимые массовые падения рабочих тестов были следующим сюрпризом, с которым мы столкнулись после проведения масштабирования до 60 одновременных тестовых потоков в 60 браузерах.
На аллюровском отчете «Timeline» это выглядело как «красные столбики» (на картинке далее). Оказалось, что это было вызвано ограничениями в производительности отдельных сервисов в рамках тестового стенда.
![](https://habrastorage.org/getpro/habr/post_images/580/84e/60b/58084e60b239a6157b54db4e8b9c496c.png)
Причин оказалось две. Стартовый «затык» был вызван массовым обращением к сервису авторизации и личному кабинету. Все браузеры одновременно начинают логиниться и перегружают сервис на стороне тестового стенда.
Последующие «столбики» оказались связаны с тем, что классы тестов у нас расположены в папках по подсистемам и junit запускал их практически одновременно, тем самым нагружая конкретную функциональную подсистему стенда и выходя в предел производительности стендовой конфигурации.
Первую проблему мы решили, обернув метод-сценарий логина в балансировщик, который ограничивал максимальное количество операций логина заданной константой.
Вторую проблему решили, включив опцию рандомного запуска тестов, которая есть у junit maven plugin.
Обязательно надо отметить, что эти проблемы были связаны с тем, что у нас не было возможностей наращивать производительность тестового стенда из-за ограниченности ресурсов, а также из-за того, что эти ресурсы большую часть времени будут простаивать.
В целом оказалось, что выстроенная система Е2Е автотестов веб-UI вполне может использоваться для суррогатного нагрузочного тестирования и скрининга производительности и устойчивости тестируемой системы в целом.
Если у вас много тестов с большим количеством операций (например, количество логируемых через Step операций составляет от 200 до 2000 при количестве тестов около 1300), то объем Allure-отчета становится более чем значительным. Если добавить сюда еще и различные вложения типа скриншотов и загруженных/выгруженных файлов, то объем идет уже на сотни мегабайт. Например, сейчас для ночного регресса на 900 тестов объем только выгружаемых на Bamboo Allure-данных составляет около 600 Мб.
Понятно, что DevOps-инженеры не в восторге от такой интенсивности пожирания дискового пространства и активно выражают недовольство, особенно в связи с необходимостью хранить данные в течение года минимум.
Выход из этой ситуации мы видим в использовании внешнего сервера для хранения и обработки отчетов, например, такого, как Allure Enterprise. Этот сервер уже платный, однако позволяет нам решить эту проблему. В настоящий момент мы проводим работы по тестированию и интеграции Allure Enterprise в наш проект.
В следующей статье мои коллеги продолжат повествование и опишут увлекательную историю автоматизации тестирования веб-сервисов с использованием продукта Smartbear SoapUI Pro. Силами двух инженеров автоматизации удалось автоматизировать порядка 500 тестовых сценариев, для этой цели пришлось реализовать дополнительный фреймворк, существенно расширяющий функциональность стандартных функций SOAP UI, но об этом чуть позже.
Статья написана в соавторстве с руководителем проекта и владельцем продукта kotalesssk.
Часть 1. Организационно-управленческая. Зачем нам была нужна автоматизация. Организация процесса разработки и управления. Организация использования
Часть 2. Техническая. Архитектура и технический стек. Детали реализации и технические сюрпризы
Часть 2-1. Реализация базового класса для всех тестов и JUnit ChainRule
Часть 2-2. Реализация процесса выгрузки файла из контейнера с браузером в тестовый фреймворк. Поиск имени загруженного браузером файла
Вторая часть публикации ориентирована в первую очередь на лидеров групп автоматизации UI end-2-end тестирования и ведущих тест-автоматизаторов. Здесь они найдут конкретные рецепты по архитектурной организации кода и развертывания, которая поддерживает массо-параллельную разработку больших групп тестов в условиях постоянной изменчивости тестовых спецификаций. В этой части приведен полный состав необходимых для UI-тестов функций с некоторыми деталями реализации, а также есть перечень сюрпризов, с которыми вы можете столкнуться.
Вот здесь вы найдете Часть 1. (Зачем нам была нужна автоматизация. Организация процесса разработки и управления. Организация использования)
![](https://habrastorage.org/getpro/habr/post_images/e47/985/776/e4798577680ae78fcb67f4c6c8976800.jpg)
Архитектура и технологический стек
Общая структура системы и ее окружения – High Level Design
Основные технологии и библиотеки, используемые в проекте:
- JavaSE8 & maven &JUnit – стек разработки;
- Selenium — библиотека для автоматизации действий веб-браузера;
- Selenide — надстройка над Selenium, имеющая элегантный API, который существенно упрощает взаимодействие с браузером и элементами страницы;
- Selenoid & GGR — реализация Selenium Grid и лоад-балансера для запуска тестов на CI сервере + преднастроенные контейнеры с браузерами;
- Yandex Allure — для отчётов на CI сервере.
Общая схема компонентов автотестов и Selenoid-инфраструктуры показана на схемах ниже, включая объясняющие комментарии:
Autotests Framework
Приложение для автоматизации регресса пользовательского интерфейса.
Поставляется в исходном коде. Использует JUnit4
run.properties
Конфигурационный файл для запуска Autotests. Определяет условное имя используемого стенда и тип исполнения — локальный или через внешние контейнеры и другие переменные.
Allure Plugin
Специальный исполняемый файл, который устанавливается на Bamboo сервер.
Создает тестовый HTML-отчет, доступный через Bamboo-сервер.
Test Report
Тестовый HTML отчет, доступный через Bamboo сервер.
Хранится на Bamboo-сервере в результатах плана на отдельной вкладке.
Bamboo
Обеспечивает запуск интеграционного тестирования как в автоматическом, так и в ручном режиме.
Хранит отчеты о тестировании в формате Allure.
ggr-server
Сервер-балансировщик Selenoid-серверов.
Обеспечивает балансировку запросов от автотестов (RemoteWebDriver) к нескольким экземплярам серверов selenium.
Docker
Докер-сервер для запуска контейнеров серверов и браузеров Selenoid.
Selenoid-server
Сервер удаленного тестирования
Обеспечивает запуск тестов в специализированных докер контейнерах с использованием «безголового» браузера.
Выполняет тесты в параллельном режиме согласно задаваемому количеству одновременных потоков.
Selenoid-ui
Сервер пользовательского интерфейса к серверу Selenoid.
Позволяет «на лету» контролировать ход тестирования через VNC.
Selenoid-webdriver
Специализированный контейнер с безголовым браузером для исполнения удаленных тестов.
Предоставляется из репозитория Selenoid.
GitLab
Хранит исходные коды приложения Autotests.
![](https://habrastorage.org/getpro/habr/post_images/c5d/cac/f81/c5dcacf817e4aa1529d7cc4d64be740d.png)
Схема работы
На следующей схеме показана общая схема работы сервиса «Автотесты» на уровне High Level Design.
![](https://habrastorage.org/webt/ye/e8/ue/yee8uexl8oqy5spoqirropxglha.png)
Инсталляция и развёртывание
Автотесты базируются на четырех серверах, один из которых – сервер исполнения, а остальные три обеспечивают запуск безголовых браузеров в контейнерах. Текущая мощность автотестов обеспечивает 60 одновременных потоков и при необходимости может быть расширена.
Текущая схема развертывания показана на следующей схеме. В целом, если вам не требуется более 20 одновременных браузеров (потоков тестирования), то вполне можно всё поставить на один сервер 12 ЯДРА + 24 ОЗУ. Мы начинали с этой конфигурации, но по мере роста требований к мощности автотестов мы выяснили эмпирически, что наиболее устойчивая и экономически выгодная конфигурация – это «типовой» сервер 12 ЯДРА + 24 ОЗУ.
По нашему опыту, для тестирования систем с веб-интерфейсом на Angular приемлемая конфигурация контейнера с браузером должна быть пол ядра + Гб памяти. Меньшая конфигурация замедляет работу браузера и даже может приводить к не идентифицируемым сбоям.
![](https://habrastorage.org/getpro/habr/post_images/152/65f/095/15265f09560a8abd9a744e5c06da1f61.png)
Структура и организация кода
Прежде чем перейти к структуре и организации кода, ещё раз перечислю те политики и ограничения, которые в конце концов и определили направления развития архитектуры:
- большое количество сложных сквозных тестовых сценариев. Высокая интенсивность разработки;
- высокая волатильность тестовых сценариев (их изменчивость);
- высокая непрерывная скорость доставки сценариев (разработка и доработка);
- начальная квалификация разработчиков автотестов;
- высокая планируемая текучка разработчиков.
С учетом перечисленного, основными архитектурными драйверами стали следующие:
- обеспечение высокой структурированности кода для легкой читаемости и управляемости;
- максимальное разделение и изоляция между собой прикладных реализаций конкретных тестов и cross-cutting классов общей функциональности;
- максимальное сокращение требуемых зависимостей для простоты внесения изменений;
- разумное переиспользование кода на уровне cross-cutting классов с допустимым возможным дублированием кода на уровне независимых групп-тестов (функциональных подсистем) для снижения зависимостей и мерж-конфликтов на уровне разработки;
- отказ от сложных фреймворков типа spring, aspectJ для сокращения времени «входа» начинающих разработчиков в проект.
В целом, такой архитектурный подход позволил реализовать независимую быструю разработку в условиях высокой изменчивости сценариев с фактически непрерывным процессом поставки новых тестов в продуктив. Архитектура и сейчас успешно выдерживает «нагрузку и доработку» несмотря на то, что в системе уже реализовано более 1500 бизнес-сценариев.
Общая структура кода и описания ключевых характерных решений приведены далее.
Общая структура кода и шаблоны разработки
Общая структура организации кода базируется на «слоистой» архитектуре (см. схему), которая в целом наследует Page Pattern, рекомендуемый разработчиками Selenium. Мы расширили этот паттерн, добавив уровень веб-элементов в основание и выделив уровень тест-сценария:
- уровень тест-класса,
- уровень тест-сценария,
- уровень веб-страницы,
- уровень веб-элемента,
- фреймворк тестирования (показан затемнением на схеме).
Каждый уровень в этой схеме имеет конкретный набор обязанностей и функциональности. Пересечение функционала между слоями или нелегитимные зависимости между ними не разрешались и были основной причиной возврата комита на доработку в рамках код-ревью перед мерж-реквестом.
Дополнительно «слоистая» структура была закреплена в делении кода по пакетам (см. схему далее).
Такая схема позволила разделить все подсистемы (которых было значительно больше, чем на схеме) между разработчиками и, как результат, дала возможность независимой разработки с практически исчезающим числом мерж-конфликтов.
Далее на схеме показана общая структура реализации тест-класса и схема распределения реализаций по пакетам проекта.
![](https://habrastorage.org/getpro/habr/post_images/c26/582/00e/c2658200ed322c07b65492aef2479074.png)
![](https://habrastorage.org/getpro/habr/post_images/caf/528/329/caf52832908c9f9d739b891d1aafb29d.png)
Уровень тест-класса
Уровень тест-класса включает один-единственный класс для конкретного отдельного теста (тест при этом описывается в тест-менеджмент-системе как линейная последовательность тест-сценариев). Тест-класс является Junit-классом с аннотацией @ Test соответствующего метода. В общем случае тест-класс реализует только один тестовый метод.
Каждый тест-класс наследуется от базового класса, задачей которого является инициализация всех TestRule в RuleChain, то есть инициализация тестовой инфраструктуры типа инициализации веб-драйвера, установки временных директорий и других «util handlers» общего назначения.
Тест-класс отвечает за организацию исполнения тестового сценария через:
- инициализацию тестовых бизнес-данных (тестовая сцена),
- последовательный вызов отдельных тестовых сценариев (Actions) согласно требуемому сценарию с передачей им инициализированных тестовых данных с шага 2.
При этом в шаге 2 должна быть линейная последовательность без каких-либо ветвлений или точек принятия решений. Шаблон реализации класса показан далее.
Уровень тест-сценария
Уровень тест-сценария отвечает за реализацию конкретных тест-сценариев согласно описанию. Тест-сценарий в этом контексте – это последовательность операций, совершаемых пользователем над системой посредством взаимодействия с элементами на веб-страницах приложений.
Основная задача класса тестового сценария:
- выполнить заданную последовательность тестового сценария посредством обращения к методам нижестоящих классов Page, используя
o переданные данные тестовой сцены,
o данные, полученные от веб-страниц (из классов Page); - выполнить требуемые бизнес-проверки успешности тестов в контексте бизнес-модели и тестовой сцены. Другими словами, класс тестового сценария НЕ проверяет разметку, наличие и доступность элементов, а оперирует бизнес-моделью тестовой сцены, фактически проводя бизнес-тестирование.
Класс тест-сценария организован как «функциональный интерфейc»:
- содержит только один паблик метод «apply» (@ Step), который:
o обеспечивает реализацию сценария как вызов последовательности «действий» (методов, предоставляемых классами Page),
o принимает на вход все необходимые бизнес-объекты, при этом НЕ СОЗДАЕТ сам никаких бизнес объектов и НЕ ВЗАИМОДЕЙСТВУЕТ напрямую ни с чем, кроме классов Page; - содержит Х приватных методов (@ Step), каждый из которых реализует конкретный отдельный шаг тестового сценария (как это описано в TMS – Test Management System);
- не взаимодействует (не вызывает методы) с другими активностями, даже активностями аналогичной подсистемы;
- не принимает на вход и не оперирует данными о логине. Другими словами, ничего не знает о ролях, из-под которых его запускают.
Такая организация класса позволяет:
- обеспечить самодокументирование отчета. Каждый метод соответствует конкретному пункту в тестовой спецификации и аннотируется аллюровской аннотацией @ Step;
- переиспользовать классы тест-сценариев в разных тестах, так как тест-сценарий 1) не создает тест-сцену и 2) не зависит от логина пользователя (операция логинов-релогинов выполняется на уровне тест-класса) 3), не зависит от других классов тест-сценариев.
Уровень веб-страницы
Уровень веб-страницы – это классический Page Pattern для тестирования в Selenium. Класс страницы содержит определения конкретных веб-элементов и набор публичных методов для выполнения неких групповых действий в контексте шагов тестового сценария.
Класс страницы непосредственно отвечает за ввод бизнес-данных в конкретные элементы интерфейса, работу с веб-драйвером (запуск JS, например) и, соответственно, осуществляет тестирование форматов, проверку разметки и структуры веб-страницы на предмет таких основных проверок, как: элемент не найден/не доступен и элемент не содержит требуемое значение.
Класс страницы также не включает и не может обращаться к другим страницам и их методам. Также классы страниц не осуществляют бизнес-проверки, ограничиваясь только структурными проверками на уровне веб-элементов в общем случае, предоставляя «наверх» на уровень «тест-сценария» полученные со страниц данные.
Уровень веб-элемента
Уровень веб-элементов включает библиотеку конечных элементов, которые могут быть на веб-странице. Он включает как конкретные примитивы, так и более сложные конгломераты, состоящие из нескольких элементов, которые мы называем виджеты. Примерами виджетов могут являться такие конструкции, как «паджинаторы», глобальные меню, различные фреймы и модальные окна, сложные элементы типа YandexMap или Youtube-проигрывателя. Веб-элементы организуют композицию с конкретными классами страниц и также ничего не знают о других элементах.
В целом, если в проекте есть некая глобальная уникальная идентификация всех элементов интерфейса с их ID, то уровень веб-элементов имеет смысл организовать как глобальную библиотеку с запросом конкретных элементов по их ID через фабрику или конфигурационные классы \ xml в спринг-библиотеке. Но не в каждом проекте это возможно.
Фреймворк тестирования
Концепция разработки автотестов, как было показано на схеме выше, базируется на идее фреймворка, при которой для всех автотестов предоставляется набор системных функций – они бесшовно интегрируются и дают возможность разработчикам автотестов концентрироваться на конкретных вопросах бизнес-реализации тест-классов.
Фреймворк включает следующие функциональные блоки:
- Rules – инициализация и финализация тестовых инфраструктурных компонентов как инициализация WebDriver и получение видеотеста (более подробно описаны далее);
- WebDriverHandlers – вспомогательные функции для работы с веб-драйвером как исполнение Java Script или доступ к логам браузера. Реализованы как набор статических state-less методов;
- WebElements – библиотека типовых веб-элементов или их групп, содержит требуемую cross-function функциональность и типовое поведение. В нашем случае к такой функциональности относится возможная проверка завершения асинхронных операций на стороне веб-браузера. Реализованы как расширения веб-элементов из библиотек Selenium и Selenide.
Инициализация тестового окружения. Rules
Ключевым классом для всех тест-классов является BaseTest, от которого наследуются все тест-классы. BaseTest-класс определяет Junit «ранер» тестов и используемый RuleChain, как показано далее. Доступ из прикладных тестовых классов к функциям, предоставляемым rule-классами, осуществляется через статические методы rule-классов с использованием идентификатора потока «thread» как ключа.
Детали реализации базовых классов для фреймворка автотестов я покажу следующей части статьи: см. Часть 2-1. Реализация базового класса для всех тестов и JUnit ChainRule.
Документирование результатов через Allure отчеты
Для получения подробных отчетов по выполненным тестам используется Allure Framework, интегрированный с Bamboo через Allure Plugin. Это обеспечивает возможность конкретным потребителям тестов (команде функционального тестирования) не только получать данные о факте падения конкретного теста, но и легко восстановить и при необходимости повторить в ручном режиме упавший тест.
Для документирования отчета по тесту используется следующий функционал:
- аннотации Allure и Junit для разметки отчета по шагам тест-сценария, а также статического описания метаданных по тесту;
- вложения Allure для прикрепления к отчету такой дополнительной информации, как видеотеста, скриншота экрана, результатов дополнительной диагностики причин падения, журнала веб-браузера, загруженных в/из браузера файлов.
Используются следующие аннотации Allure и Junit для разметки отчета.
- На уровне тест-класса:
o @ Feature – имя функциональной подсистемы, к которой относится тест;
o @ Story – имя конкретного тест-сценария;
o @ Owner – имя разработчика, который последним вносил изменения в тест;
o @ TmsLink – ссылка на описание теста в используемой «test management system». - На уровне тест-метода (@ Test) тест-класса:
o @ DisplayName – полное наименование тест сценария. Отличается от @ Story тем, что позволяет разделять одинаковые сценарии, которые отличаются только составом тестовой сцены; - на уровне методов, которые соответствуют конкретным шагам тестового сценария:
o @ Step – осмысленное наименование тестового шага которое отражает бизнес-суть происходящего.
Наименование шагов тестирования через @ Step позволяет создать подробный отчет, который полностью повторяет все шаги и пункты, описанные в тестовом сценарии. Это позволяет пользователям автотестов легко трассировать ход тест-сценария и помогает определять точку падения.
В целом, использование Allure Framework оказалось очень полезным и легким, за исключением некоторых особенностей, связанных с огромным количеством данных, которые генерируются в нашем случае для большого количества длинных сложных тест-сценариев (описано далее в разделе «Ретроспектива»).
Что, возможно, стоило сразу сделать по-другому? Использовать Spring Framework
Несмотря на то, что при реализации автотестов, мы умышленно отказались от использования Spring Core, в общем случае я считаю его использование оправданным и рабочим. Реализованный прототип показал, использование Spring Core вполне работает для следующих задач (хотя мы конечно на полную не тестировали):
- инициализации тестовой сцены;
- инициализации веб-страниц и элементов.
Единственной особенностью является необходимость использования «по умолчанию» контекста scope уровня prototype.
Инициализация тестовой сцены. В автотестах тестовая сцена инициализируется классическим методом создания экземпляров классов непосредственно в тест-классе через простой new или фабрики объектов. Тут вполне разумно использовать инициализацию либо через конфигурационные классы, либо через внешние xml или проперти файлы. Плюсы следующие: это 1) упрощает код ревью, так как изменения тестовой сцены более не относятся к ключевым классам, 2) позволяет подключать разные тестовые сцены для разных стендов или других условий. Сейчас пункт 2 у нас не используется.
Инициализация веб-страниц и элементов. Инжекция классов веб-страниц и веб-элементов работает в силу отложенной ленивой инициализации selenide веб-элементов.
Таким образом, появляется возможность создать библиотеку веб-элементов и страниц в соответствии с некой глобальной спецификацией пользовательского интерфейса и получать в коде ссылки на эти элементы не по абсолютному пути и id, а по проектному id согласно спецификации.
Сразу оговорюсь, что тестировал я это не очень тщательно, фактически ограничившись тестовой реализацией по этому принципу «страниц» логина и страницы «инфо» пользователя.
Ретроспектива. Сюрпризы
Здесь я описал неожиданные сюрпризы, возникшие в ходе развития проекта, плюс некоторые соображения на счет того, что можно было сделать лучше, если бы «не».
Не дружественная к Selenium разметка фронтенда (Angular)
Одним из самых серьезных сюрпризов, с которым мы столкнулись, оказалась «плывущая» разметка страницы, которая приводила к тому, что после обновления Angular приложений, тесты падали, так как Selenium не мог найти элементы, потому что изменились их ИД (id, class или ng-model) или пути (для XPath). Это приводило к нестабильности тестов и неоднозначности причин падения.
К сожалению, данная проблема оказалась в целом не решаемой. Мы обходили ее организационными мероприятиями: 1) при начале тестирования нового кандидата в релиз все разработчики автотестов концентрируются на быстром устранении и доработке в части правки значений локаторов веб-элементов; 2) в конце концов мы пришли к использованию относительных XPath, которые, увы, совсем не улучшают производительность.
В идеале борьба с такими сюрпризами – в изначально встраиваемой практике уникального именования всех элементов интерфейса согласно некоему словарю, который может быть связан с глобальной доменной структурой бизнес-данных и дизайном пользовательского интерфейса на уровне всего проекта.
Отсутствие данных об имени «download» файла
Для «выгрузки» файлов из тестируемой системы мы используем следующие решения:
- для «локального» режима запуска (запуск непосредственно на рабочем ПК) – при инициализации «локального» браузера ему в качестве параметра передается имя временной локальной папки. Далее файл читается напрямую из локальной папки для последующего анализа;
- для «удаленного» режима (через Bamboo) файл «забирался» из контейнера с браузером через фичу solenoid сервера: selenoid-host.example.com:4444/download/{SESSION_ID}/{FILE_NAME}
Детали описаны в документации.
Для обоих режимов для доступа к файлу необходимо было знать имя файла. В этом и был сюрприз, ибо мы никак не могли знать, под каким именем загрузится файл на диск в контейнере. Как мы организовали загрузку файла из контейнера с браузером в автотесты, можно будет почитать в следующей части статьи.
Засорение тестовых баз данных
С засорением тестовых баз данных мы столкнулись достаточно быстро. В первую очередь это было связано с тем, что для «чистоты» прохождения тестовых сценариев тестовое окружение генерировалось динамически на самом стенде. Например, для проверки функции добавления Объекта А пользователя в его личный кабинет нам последовательно надо было создать нового пользователя, фейковый договор, Объект А, разместить его по конкретному адресу, и только после выполнения всех предварительных тест-сценариев (в которых создавалась тестовая сцена) выполнить тест-сценарий связывания Объекта А с пользователем.
Легко понять, что через некоторое время по конкретному адресу, используемому для размещения объектов, появлялись тысячи и десятки тысяч этих объектов. Это начинало вызывать проблемы производительности при выполнении поисковых операций по Объектам по адресу.
Это самый яркий пример, с которым мы столкнулись. В данном случае мы просто переезжаем регулярно на «новый» адрес, и в целом процедура ротации дефолтной сцены должна проводиться регулярно, иначе возможно возникновение неприятных сюрпризов, которые выражаются в растущем времени прохождения тестов и падении тестов по таймауту.
Высокая продолжительность тестов
С этой проблемой мы столкнулись очень неожиданно в ходе роста количества одновременно исполняемых тестов. Думаю, мы просто не задумывались над этим. Время прохождения полного набора тестов (~ 1000 полноценных тестов) начало приближаться к критической отметке в 6 часов.
Для увеличения количества одновременно запускаемых тестов мы перешли на Selenoid-ggr. Это позволило нам поднять количество одновременных потоков junit с 20 (один Selenoid-сервер) до 60 (три сервера), после чего мы уперлись в производительность тестового стенда. «Внезапно» оказалось, что стенд тестирования стабильно работает не более с чем 60 одновременными сессиями.
Конечно, производительность можно увеличить ресурсами, но проблема, что все эти ресурсы нужны исключительно в течение 3-4 часов, когда проводится полное ночное тестирование или периодическое preQA-тестирование. Остальное время ресурсы простаивают. Тут, конечно, сразу напрашивается AWS c почасовой арендой серверов, тем более что Selenoid-решение отлично укладывается в этот подход, но если ваш стенд не расположен одной AWS зоне с Selenoid, вы можете разориться на in/out трафике.
В любом случае, вопрос масштабирования и производительности как серверов тестирования, так и самого стенда тестирования при построении систем автотестирования с большим количеством E2E тестов следует рассматривать с начала проекта. Для этого стоит опираться на требования к максимальному времени прохождения полного регресса и preQA.
Стартовый «шторм» при одновременном исполнении большого количества тестов
Необъяснимые массовые падения рабочих тестов были следующим сюрпризом, с которым мы столкнулись после проведения масштабирования до 60 одновременных тестовых потоков в 60 браузерах.
На аллюровском отчете «Timeline» это выглядело как «красные столбики» (на картинке далее). Оказалось, что это было вызвано ограничениями в производительности отдельных сервисов в рамках тестового стенда.
![](https://habrastorage.org/getpro/habr/post_images/580/84e/60b/58084e60b239a6157b54db4e8b9c496c.png)
Причин оказалось две. Стартовый «затык» был вызван массовым обращением к сервису авторизации и личному кабинету. Все браузеры одновременно начинают логиниться и перегружают сервис на стороне тестового стенда.
Последующие «столбики» оказались связаны с тем, что классы тестов у нас расположены в папках по подсистемам и junit запускал их практически одновременно, тем самым нагружая конкретную функциональную подсистему стенда и выходя в предел производительности стендовой конфигурации.
Первую проблему мы решили, обернув метод-сценарий логина в балансировщик, который ограничивал максимальное количество операций логина заданной константой.
@Step("Логин: Получение доступа к логину")
public void apply(User user) {
try {
LoadCounter.get(); // арендуем право логина. Операция останавливает поток и ждет
try {
applyLogin(user); //выполняем логин
} catch (Throwable t) {
throw t; // прокидываем далее тестовые ошибки типа если они случились при логине
} finally {
LoadCounter.release(); // освобождаем право логина
}
} catch (LoadCounterException e) { // если в течении требуемого времени не дождались то ошибка
throw new StandRuntimeException("Can not get loadcounter for Login action.", e);
}
}
Вторую проблему решили, включив опцию рандомного запуска тестов, которая есть у junit maven plugin.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>….</version>
<configuration>
<!-- Defines the order the tests will be run in. Supported values are "alphabetical",
"reversealphabetical", "random","hourly", "failedfirst", "balanced" and "filesystem". -->
<runOrder>random</runOrder>
…
Обязательно надо отметить, что эти проблемы были связаны с тем, что у нас не было возможностей наращивать производительность тестового стенда из-за ограниченности ресурсов, а также из-за того, что эти ресурсы большую часть времени будут простаивать.
В целом оказалось, что выстроенная система Е2Е автотестов веб-UI вполне может использоваться для суррогатного нагрузочного тестирования и скрининга производительности и устойчивости тестируемой системы в целом.
Высокая нагрузка на Bamboo-сервер и его хранилище
Если у вас много тестов с большим количеством операций (например, количество логируемых через Step операций составляет от 200 до 2000 при количестве тестов около 1300), то объем Allure-отчета становится более чем значительным. Если добавить сюда еще и различные вложения типа скриншотов и загруженных/выгруженных файлов, то объем идет уже на сотни мегабайт. Например, сейчас для ночного регресса на 900 тестов объем только выгружаемых на Bamboo Allure-данных составляет около 600 Мб.
Понятно, что DevOps-инженеры не в восторге от такой интенсивности пожирания дискового пространства и активно выражают недовольство, особенно в связи с необходимостью хранить данные в течение года минимум.
Выход из этой ситуации мы видим в использовании внешнего сервера для хранения и обработки отчетов, например, такого, как Allure Enterprise. Этот сервер уже платный, однако позволяет нам решить эту проблему. В настоящий момент мы проводим работы по тестированию и интеграции Allure Enterprise в наш проект.
To be continued
В следующей статье мои коллеги продолжат повествование и опишут увлекательную историю автоматизации тестирования веб-сервисов с использованием продукта Smartbear SoapUI Pro. Силами двух инженеров автоматизации удалось автоматизировать порядка 500 тестовых сценариев, для этой цели пришлось реализовать дополнительный фреймворк, существенно расширяющий функциональность стандартных функций SOAP UI, но об этом чуть позже.
Статья написана в соавторстве с руководителем проекта и владельцем продукта kotalesssk.
Часть 1. Организационно-управленческая. Зачем нам была нужна автоматизация. Организация процесса разработки и управления. Организация использования
Часть 2. Техническая. Архитектура и технический стек. Детали реализации и технические сюрпризы
Часть 2-1. Реализация базового класса для всех тестов и JUnit ChainRule
Часть 2-2. Реализация процесса выгрузки файла из контейнера с браузером в тестовый фреймворк. Поиск имени загруженного браузером файла
У нас есть вакансии, приходите к нам!
- Стажёрская программа «Автоматизированное тестирование»
- Стажёрская программа «Нагрузочное тестирование»
- Инженер по тестированию-стажер
- Ведущий специалист по функциональному тестированию
- Ведущий инженер по автоматизированному тестированию