Русский
Русский
English
Статистика
Реклама

Блог компании нтц протей

Безопасные города без зоопарка

29.12.2020 18:11:47 | Автор: admin
image

Ковид и его последствия подкосили фундамент ценностной пирамиды людей. Он ударил по самому важному безопасности. В 21 веке бояться простуды несолидно, но приходится. И приходится находить эффективные решения в непрогнозируемом пространстве. Многие ли ваши планы 2020 сбылись? А смог ли хоть один риск-менеджер предсказать всемирный карантин? Не смог, но современные знания и технологии минимизировали, на наш взгляд, последствия новой эпидемиологической реальности.

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

С чем едят АПК Безопасный город

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

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

А теперь попробуйте решить задачу интеграции уже установленных в регионе нескольких систем видеонаблюдения от разных вендоров, разного типа и времени производства в рамках 1 системы. Представили? Как анализировать этот клубок видеопотоков, как прикрутить к этому видеоаналитику? И как его сводить к единому стандарту в рамках 1 системы?

Реализовать в ядре все протоколы, включая проприетарные, значит перманентно отодвигать сроки внедрения и неразумно раздувать бюджет проекта. А ведь это касается не только региональных и муниципальных систем видеонаблюдения, но и систем оповещения населения, и мониторинга транспорта, тревожных кнопок, экологического контроля, прогнозирования и так далее. Список колоссальный! Что можно сделать?



По существу

Обратимся к нормативным документам. Единые требования к техническим параметрам сегментов аппаратно-программного комплекса Безопасный город (28.06.2017) лаконично констатируют, что в ядре платформы Безопасный город должно быть 2 ключевые подсистемы: Региональная интеграционная платформа (РИП) и Единый центр оперативного управления (ЕЦОР). Просто, недешево и минимально функционально. Только анализ данных и управление, остальное интеграции.

Вроде необходимый минимум обеспечен, можно строить Безопасные города. Однако на практике подобная задача становится практически нерешаемой, если стоит цель обеспечения безопасности населения, так как в каждом регионе уже действуют разные системы и решения, которые нужно собрать в кучу. Это оказывается весьма трудоемко и не всегда технически красиво. Сделай то, не знаю, что, но, чтобы работало.

Например, в Курске при в рамках работ по созданию АПК Безопасный город была проведена работа по интеграции с имеющимся сервером видеонаблюдения, причем изначально стоял выбор между 2 вариантами: быстро и дешево интегрироваться по проприетарному протоколу, или доработать ядро системы и реализовать намного более универсальный протокол ONVIF. Осознанным выбором стало развитие системы и реализация ONVIF. Это стало важным шагом в развитии универсального решения.

Внедрение АПК Безопасный город постепенно набирает обороты, однако, есть проблемы. Это и затягивание сроков сдачи, и искажение, увеличение, раздувание в процессе задач по контрактам и банальный недостаток финансирования. С чем связанны эти особенности реализации? Как мы можем влиять на внедрение?

Есть недостатки в нормативной базе, в разрозненности систем, необходимости их интеграции между собой, в готовности регионов к внедрению и отсутствии единых стандартов интеграции, межсистемного и межведомственного взаимодействия.

Добавим ингредиентов

Вспомним, что сейчас для закладки Безопасного города нужно только 2 ключевые подсистемы: РИП для создания единого информационного пространства и ЕЦОР для управления потоками данных и управления ситуацией. Наш опыт разработки и внедрение АПК Безопасный город говорит о том, что этого крайне мало и в ядро системы необходимо добавить несколько критически важных подсистем. Без этих ингредиентов кашу Безопасных городов нормально не заваришь. Критически-важную инфраструктуру надо создавать по новому рецепту.

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

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



Поэтому нужна собственная интеграционная платформа видеонаблюдения именно в ядре БГ, чтоб исключить многочисленные проблемы сопряжения с местными решениями. Ведь видеонаблюдение сегодня позволяет не только получить картинку, но и подключить видеоаналитику или взять управление конкретной камерой на себя. Считается, что можно легко прикрутить к имеющимся камерам видеоаналитику и наслаждаться результатом. Не получается. Везде, где мы внедряли АПК Безопасный город, нужна была серьезная подстройка камер для конкретных задач.

В реальности с такой задачей нам пришлось столкнуться в Омске, где мы отвечали за внедрение части сервера видеонаблюдения (СВН). Самая большая работа была проделана именно в плане построения интеграционной шины. Были реализованы интеграции с уже имеющимися СВН и видеорегистраторами сторонних производителей, используя их, проприетарные протоколы. Здесь, к сожалению, ONVIF не помог.

Была проведена работа по обеспечению взаимодействия с системами видеоаналитики различных вендоров, включая систему распознавание номеров (ГРЗ), которая работает на обычных камерах видеонаблюдения. Далее ее сработки передаются в АИС регионального подразделения МВД. В целом в систему были заведены потоки всего спектра имеющихся в регионе устройств: от аналоговых камер до рубежей системы фотовидеофиксации. Апогеем данного проекта является интеграция видеопортала и РИП АПК БГ. Это позволило передавать информацию о всех камерах региона, транслировать онлайн-видео, архив, а также передавать события по срабатыванию видеоаналитики в единый центр.

Во-вторых, важным элементом в приготовлении безопасных городов является подсистема мониторинга стационарных и подвижных объектов. Надо и пробки видеть, и доступные транспортные средства, и датчики мониторинга на неподвижных объектах, и про экологию не забывать. Только по камерам оценить резервы и спрогнозировать ситуацию не получится. И все эти данные также приходят из разных подсистем и все их нужно объединять в рамках ядра АПК БГ, и не просто объединять, а где-то и управлять. Вот и получатся, что подсистема мониторинга просто обязана входить в список продуктов, необходимых для приготовления первоклассного АПК БГ.

И третьей ключевой подсистемой АПК БГ, пока не входящей в состав ядра является система оповещения населения. В России она давно создается и РАСЦО, и КСЭОН, но при интеграции с безопасным городом возникают проблемы и с управлением этим хозяйством, и с обратной связью. Сопряжение есть, отправка команды вроде бы идет, но понять, дошло ли сообщение до оператора внешней системы оповещения, и услышали ли его люди, зачастую невозможно.

При возникновении нештатной ситуации оповещение не просто должно быть эффективно, но и сфокусировано ровно на той зоне, где оно необходимо. Большим плюсом является интеграция оповещения с ГИС и с системой прогнозирования. Например, при пожаре на предприятии будет проведен расчет зоны поражения и оповещены жители только тех районов, которые попадают в эту зону. Управляющая платформа оповещением должна быть именно в БГ. Кстати, системы оповещения в пандемию задействованы в процессе распространения информации, например, о мерах безопасности.



Примером реализации системы экстренного оповещения может служить проект в Тюменской области. В настоящее время вся область и все муниципалитеты покрыты системой громкоговорящего оповещения, а для ряда предприятий реализована подсистема мониторинга химически-опасных объектов. В регионе проведена интеграция с имеющимся оборудованием различных производителей, а также реализован алгоритм перехвата телерадиовещания и оповещение должных лиц по телефону. Система имеет распределенную архитектуру, в ЦОД находится отказоустойчивый кластер, а также еще в каждом муниципалитете по локальному серверу. В целом проект призван обеспечить гарантированную передачу населению важной информации в случае ЧП и ЧС. И его интеграция в безопасный город это только вопрос времени и актуализации нормативной документации.

Что станет изюминкой?

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

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

Но как можно предсказать будущее и проверить, что подсистема принятия решений работает? Здесь приходят на помощь наработки министерств РФ с многочисленными методикам анализа ситуаций и расчета показателей. Основаны они на реальном опыте и дают разработчикам всю необходимую информацию о формулах и особенностях расчета тех или иных ситуаций.
Интеграция нескольких источников данных в ядре Безопасного города помогает получить актуальные на данный момент величины (объем выбросов, тип вещества, направление ветра, температура) и на основании них выстроить наиболее эффективный план реагирования, алгоритм действий.

Реализованная в виде программы методология расчета учитывает полученные данные строит наглядный прогноз развития событий. Есть прогноз, можно подсказать и решение, одобренное или измененное ответственным лицом. Чем больше знает и умеет система прогнозирования и помощи в принятии решений, тем спокойнее будут спать граждане.



И на десерт

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

Безопасный город это та основа, на которой мы строим цифровое будущее России. Цифровая трансформация приближается уже сегодня внедрением автоматизированных решений по безопасности. Концепция моновендорного ядра с расширенным списком ключевых подсистем представляется самой перспективной сейчас. Хочется вынести ее на обсуждение профессионального сообщества Хабра и услышать, возможно, альтернативные мнения разработчиков.

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

Делаем новую версию API. Быстро и легко

09.04.2021 14:13:02 | Автор: admin

Коммуникация правит миром. Взаимодействие необходимо и между людьми, и между программным обеспечением. Хотите адекватного ответа на ваш запрос к приложению? API вам в помощь! Необходимость в реализации API возникает практически во всех проектах, и со временем мы задумываемся, можно ли улучшить текущий API? Последовательность конкретных шагов и реальные примеры наш рецепт создания рабочего API-проекта.

Первый вопрос, который нужно задать: А точно ли стоит реализовать новую версию API? Возможно, уже работающая версия отвечает всем необходимым вам критериям. Но если вы уже для себя ответили да на поставленный вопрос, то читайте дальше и найдете наш ответ. Если вы ответили нет, то тоже читайте дальше и применяйте наш опыт проектирования и реализации в следующих ваших проектах.

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

Какие шаги можно предпринять для решения этих задач, и как выйти на новый уровень зрелости API можно узнать в этой статье. Реальный алгоритм действий при разработке новой реализации API, описанный здесь, поможет в создании собственного проекта, а возможно станет предметом дискуссии. Комментарии профессионалов приветствуются!

Рассмотрим на примере API для работы с котиками то, как мы совершенствовали один из наших проектов.

Как понять, что стоит реализовать новую версию API

Для того чтобы понять, что API пора спасать, необходимо пройтись по следующим пунктам:

  • определить уровень зрелости API;

  • проверить, есть ли у API версионность;

  • проверить наличие документации API.

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

Определим уровень зрелости API

Для этого идеально подходит модель Леонарда Ричардсона, в которой он выделяет четыре уровня зрелости API:

  • Уровень 0: Один URI и один HTTP метод (в основном метод POST);

  • Уровень 1: Несколько URI и один HTTP метод;

  • Уровень 2: Несколько URI, каждыи из которых поддерживает разные HTTP методы;

  • Уровень 3: HATEOAS. Ресурсы сами описывают свои возможности и взаимосвязи.

Если API соответствует 0 или 1 уровню зрелости, то определенно есть куда расти, потому что:

  • Если используется один URI, то не понятно с каким ресурсом работаем;

  • Не понятно, что делаем с ресурсом, так как используется один HTTP метод;

  • Использование одного URI создает трудности с документацией API;

  • Использование одного URI создает трудности с логированием входящих запросов;

  • Из-за использования одного URI, информация о типе ресурса передается в теле запроса.

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

Проверим, есть ли у API версионность

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

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

Рассмотрим 3 основных способа версионирования API и разберем подробнее каждый из них. Современные разработчики выделяют следующие способы:

- Использование разных URI (Uniform Resource Identifier);

- Использование параметра запроса;

- Использование заголовка, Accept Header/Media Type.

Каждый из приведенных способов версионирования имеет свои плюсы и минусы. И для каждого конкретного случая реализации API необходимо оценить и выбрать оптимальный способ или их комбинацию.

  1. Использование разных URI простой в проектировании, реализации и документировании способ версионирования. Однако он имеет целый ряд недостатков:

  • приводит к загрязнению URI, так как префиксы и суффиксы добавляются к основным строкам URI;

  • разбивает существующие URI, то есть все клиенты должны обновиться до нового;

  • приводит к увеличению размер HTTP кэша для хранения нескольких версии;

  • создает большое количество дубликатов URI, может снизить производительность приложения из-за увеличения количества обращении к кэшу;

  • является крайне негибким и не позволяет просто изменить ресурс или небольшой их набор.

Пример:

GET v1/cats/{name}

2.Использование параметра запроса позволяет легко документировать версионность и рекомендуется к использованию в случае, если важно HTTP кэширование. Однако и этот способ приводит к загрязнению пространства URI.

Пример:

GET cats/{name}?version=v1

3. Использование заголовка и Accept Header/Media Type также легко документировать и, в отличие от предыдущих способов не приводит к загрязнению пространства URI. Но и у этого способа выделяют несколько минусов:

-приводит к неправильному использованию заголовков, так как они изначально предусматривались не для версионирования;

-требует для управления версиями на основе заголовка и типа медиа использования таких инструментов, как Postman, или создания автоматизированных средств, добавляющих необходимый заголовок в HTTP запрос.

Пример:

GET cats/{name}

Headers: version=v1

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

Проверим наличие документации API

Даже к фену удобно иметь описание, не говоря уже о серьезной проекте разработки ПО. Поэтому наглядное описание API всегда удобно для использования как backend, так и frontend разработчиками. Документация может быть реализована, например, с помощью Swagger (фреймворк для спецификации RESTful API), он дает возможность не только интерактивно просматривать спецификацию, но и отправлять запросы с помощью Swagger UI:

Немного окунувшись в теорию и предварительный анализ, переходим непосредственно к проектированию и реализации API. Наше API отвечал следующим критериям:

  • соответствует 0 уровню зрелости (Один URI и один HTTP метод);

  • невозможно достоверно установить, с каким ресурсом API работает и какие функции выполняет;

  • отсутствуют автоматизированные средства документации API, что приводит к неполной документации API или ее полному отсутствию для некоторых запросов;

  • появляются сложности с поддержкой обратной совместимости, так как нет версионности API.

Для большего понимания того, что не нравилось, приведем пример того, как выглядел наш API.

Пример:

POST /cats - должен вернуть котика по имени Пушок (гарантируется, что у

requestBody: { котиков уникальные имена);

"name": "Pushok"

}

POST /cats - должен вернуть вернуть список белых котиков;

requestBody: {

"color": "white"

}

Цели реализации нового API

Если в предыдущем пункте мы уверенно выявили хотя бы один пункт в существующем API, то самое время переходить к реализации нового. Основными целями, которые преследует разработчики при создании API являются:

  • Ускорение процесса клиентской и серверной разработки;

  • Снижение временных затрат на поддержку и развитие API;

  • Добавление автогенерации документации API;

  • Поддержка версионности API для упрощения поддержки обратной совместимости с помощью версионности API.

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

Повышаем уровень зрелости API

Для решения вышеперечисленных проблем и достижения целей было принято решение о переходе на 2 уровень зрелости API. Это позволило понять, с каким ресурсом идет текущая работа и что с ним необходимо сделать, плюс появилась возможность документировать API.

Для того, чтобы повысить API с 0 на 2 уровень зрелости были проанализированы все текущие проблемы и выделены следующие пункты:

1.Разделение текущего API на смысловые части для выделения соответствующего ресурса для каждой части;

2.Использование методов, соответствующих действиям над ресурсами: GET, POST, PUT, DELETE;

3.Обозначение ресурса во множественном числе;

Пример:

GET /cats - должен вернуть список котиков

GET /cats/Pushok - должен вернуть котика по имени Пушок

(гарантируется, что у котиков уникальные имена)

4. Указание фильтрации в параметрах.

Пример:

GET /cats?color=white - должен вернуть список белых котиков

Добавляем версионность

После повышения зрелости API выходим на новый этап и выбираем способ версионирования. Для текущего проекта был выбран способ версионирования с использованием собственного заголовка. Это решение не попадает в пункт неправильное использование заголовков, так как будет использоваться собственный заголовок. Для удобства было решено указывать версии вида 2.n.

Для начала реализуем контроллер:

После этого для реализации версионности, создадим enum:

Далее создадим конвертер, используя внутренний Spring Framework интерфейс Converter<S,T>. Он преобразует исходный объект типа S в целевой типа T, в нашем случае преобразует текстовое значение версии в тип Enum ApiVersion до попадания в контроллер:

Если в заголовке было отправлено значение 2.0, до контроллера оно дойдет уже в виде v2. При такой реализации вместо того, чтобы перечислять все доступные версии, можно будет указать в заголовке, что ожидается enum. А контроллер после добавления версионирования будет выглядеть так:

При дальнейшем развитии API и реализации новых версии, список всех существующих версии будет храниться в enum. Когда для конкретной новой версии потребуется уникальная реализация, то в контроллере можно будет указать версию явно.

Указание версии было решено сделать обязательным.

Документируем

И наконец переходим к документированию. Критерии выбора конкретного инструмента были основаны на собственном опыте и дискуссии с коллегами-разработчиками. В ходе обсуждения был выбран популярный и широко используемый инструмент автогенерации API Swagger. Он не является единственным для решения задач документирования, но в нашем случае был признан оптимальным, так как бесплатен, прост для освоения и обладает необходимым набором функций для грамотного документирования API.

Рассмотрим пример реализации документации для созданного выше контроллера. Для этого реализуем интерфейс:

С его помощью можно добавить название и описание API текущего контроллера, описание каждого запроса, плюс есть возможность указать схему тела запроса. Остальную информацию Swagger получает из аннотаций контроллера.

Перейдя в Swagger UI увидим задокументированное API:

Получим более подробную информацию:

Преимущества новой версии API

На примерах выше был продемонстрирован переход с 0 уровня зрелости API на 2 уровень зрелости согласно модели Ричардсона, благодаря этому мы получили:

  • повышение уровня зрелости API, что дало понимание, с каким ресурсом мы работаем и что с ним делаем;

  • версионирование, которое позволит вносить изменения в текущий API;

  • удобное документирование и появление внятной документации, да и документации вообще.

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

Скорость разработки при переходе на новую версию API значительно возросла, ведь разработчикам стало значительно удобнее и приятнее работать. Наш путь разработки оказался вполне рабочим и его смело можно рекомендовать для применения в реальных проектах. А может в вашем арсенале есть другие варианты? Готовы обсудить!

Подробнее..

Пожалуй, лучшая архитектура для UI тестов

16.10.2020 18:23:47 | Автор: admin

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


Привет, Хабр! Меня зовут Диана, я руководитель группы тестирования пользовательских интерфейсов, автоматизирую веб и десктоп тесты уже пять лет. Примеры кода будут на java и для web, но, на практике проверено, подходы применимы и к питону с десктопом.

В начале было...


Вначале было слово, и слов было много, и заполняли они все страницы равномерно кодом, не обращая внимания на эти ваши архитектуры и принципы DRY (dont repeat yourself не надо повторять код, который вы уже написали три абзаца выше).

Простыня


На самом деле, архитектура портянки, она же простыня, она же сваленный в кучу неструктурированный код, равномерно заполняющий экран, не так уж плоха и вполне применима в следующих ситуациях:

  • Быстроклик в три строчки (ну ладно, в двести три) для очень маленьких проектов;
  • Для примеров кода в мини-демо;
  • Для первого кода в стиле хелоу ворд среди автотестов.

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

import com.codeborne.selenide.Condition;import com.codeborne.selenide.WebDriverRunner;import org.testng.annotations.Test;import static com.codeborne.selenide.Selenide.*;public class RandomSheetTests {    @Test    void addUser() {        open("https://ui-app-for-autotest.herokuapp.com/");        $("#loginEmail").sendKeys("test@protei.ru");        $("#loginPassword").sendKeys("test");        $("#authButton").click();        $("#menuMain").shouldBe(Condition.appear);        $("#menuUsersOpener").hover();        $("#menuUserAdd").click();        $("#dataEmail").sendKeys("mail@mail.ru");        $("#dataPassword").sendKeys("testPassword");        $("#dataName").sendKeys("testUser");        $("#dataGender").selectOptionContainingText("Женский");        $("#dataSelect12").click();        $("#dataSelect21").click();        $("#dataSelect22").click();        $("#dataSend").click();        $(".uk-modal-body").shouldHave(Condition.text("Данные добавлены."));        WebDriverRunner.closeWebDriver();    }}


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

PageObject


Слышали слухи, что PageObject устарел? Вы просто не умеете его готовить!

Основной рабочей единицей в этом паттерне является страница, то есть законченный набор элементов и действий с ними, например, MenuPage класс, описывающий все действия с меню, то есть клики по вкладкам, раскрытие выпадающих подпунктов и так далее.



Чуть сложнее составить PageObject для модального окошка (для краткости модалки) создания объекта. Набор полей класса понятен: все поля ввода, чекбоксы, выпадающие списки; а для методов есть два варианта действий: можно сделать как универсальные методы заполни все поля модалки, заполни все поля модалки рандомными значениями, проверь все поля модалки, так и отдельные методы заполни название, проверь название, заполни описание и так далее. Что использовать в конкретном случае определяется приоритетами подход один метод на всю модалку увеличивает скорость написания теста, но по сравнению с подходом по одному методу на каждое поле сильно проигрывает в читаемости теста.

Пример
Составим общий Page Object создания пользователей для обоих видов тестов:
public class UsersPage {    @FindBy(how = How.ID, using = "dataEmail")    private SelenideElement email;    @FindBy(how = How.ID, using = "dataPassword")    private SelenideElement password;    @FindBy(how = How.ID, using = "dataName")    private SelenideElement name;    @FindBy(how = How.ID, using = "dataGender")    private SelenideElement gender;    @FindBy(how = How.ID, using = "dataSelect11")    private SelenideElement var11;    @FindBy(how = How.ID, using = "dataSelect12")    private SelenideElement var12;    @FindBy(how = How.ID, using = "dataSelect21")    private SelenideElement var21;    @FindBy(how = How.ID, using = "dataSelect22")    private SelenideElement var22;    @FindBy(how = How.ID, using = "dataSelect23")    private SelenideElement var23;    @FindBy(how = How.ID, using = "dataSend")    private SelenideElement save;    @Step("Complex add user")    public UsersPage complexAddUser(String userMail, String userPassword, String userName, String userGender,                                     boolean v11, boolean v12, boolean v21, boolean v22, boolean v23) {        email.sendKeys(userMail);        password.sendKeys(userPassword);        name.sendKeys(userName);        gender.selectOption(userGender);        set(var11, v11);        set(var12, v12);        set(var21, v21);        set(var22, v22);        set(var23, v23);        save.click();        return this;    }    @Step("Fill user Email")    public UsersPage sendKeysEmail(String text) {...}    @Step("Fill user Password")    public UsersPage sendKeysPassword(String text) {...}    @Step("Fill user Name")    public UsersPage sendKeysName(String text) {...}    @Step("Select user Gender")    public UsersPage selectGender(String text) {...}    @Step("Select user variant 1.1")    public UsersPage selectVar11(boolean flag) {...}    @Step("Select user variant 1.2")    public UsersPage selectVar12(boolean flag) {...}    @Step("Select user variant 2.1")    public UsersPage selectVar21(boolean flag) {...}    @Step("Select user variant 2.2")    public UsersPage selectVar22(boolean flag) {...}    @Step("Select user variant 2.3")    public UsersPage selectVar23(boolean flag) {...}    @Step("Click save")    public UsersPage clickSave() {...}    private void set(SelenideElement checkbox, boolean flag) {        if (flag) {            if (!checkbox.isSelected()) checkbox.click();        } else {            if (checkbox.isSelected()) checkbox.click();        }    }}

А в классе тестов пользователей распишем тест с комплексными действиями:
    @Test    void addUser() {        baseRouter.authPage()                .complexLogin("test@protei.ru", "test")                .complexOpenAddUser()                .complexAddUser("mail@test.ru", "pswrd", "TESTNAME", "Женский", true, false, true, true, true)                .checkAndCloseSuccessfulAlert();    }

И с подробными действиями:
    @Test    void addUserWithoutComplex() {        //Arrange        baseRouter.authPage()                .complexLogin("test@protei.ru", "test");        //Act        baseRouter.mainPage()                .hoverUsersOpener()                .clickAddUserMenu();        baseRouter.usersPage()                .sendKeysEmail("mail@test.ru")                .sendKeysPassword("pswrd")                .sendKeysName("TESTNAME")                .selectGender("Женский")                .selectVar11(true)                .selectVar12(false)                .selectVar21(true)                .selectVar22(true)                .selectVar23(true)                .clickSave();        //Assert        baseRouter.usersPage()                .checkTextSavePopup("Данные добавлены.")                .closeSavePopup();    }

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


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

Жаль только, что абсолютно одинаковые интерфейсы на разных платформах встречаются редко.

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

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

А что еще бывает?


Как ни странно, не PageObjectом единым!

  • Часто встречается паттерн ScreenPlay, о котором можно почитать например тут. У нас он не прижился, так как использовать bdd-подходы без вовлечения людей, не умеющих читать код бессмысленное насилие над автоматизаторами.
  • У js-фреймворков появляются свои собственные упрощающие жизнь подходы, помимо обязательного PageObject, но при их разнообразии говорить о чем-то устоявшемся и универсальном, мне кажется, слишком смело.
  • Можно написать и что-то свое, например, фреймворк на основе ModelBaseTesting, о чем хорошо рассказали в докладе с гейзенбага докладе с гейзенбага. Этот подход используется в первую очередь в проектах со сложносвязанными объектами, когда обычных тестов не хватает для проверки всех возможных комбинаций состояний и взаимодействий объектов.

А я вам расскажу подробнее про Page Element, позволяющий уменьшить количество однотипного кода, повысив при этом читаемость и обеспечив быстрое понимание тестов даже у тех, кто не знаком с проектом. А еще на нем (со своими блекджеками и преферансами, конечно!) построены популярные не-js фреймворки htmlElements, Atlas и епамовский JDI.

Что такое Page Element?



Для построения паттерна Page Element начнем с самого низкоуровневого элемента. Как говорит Викисловарь, виджет программный примитив графического интерфейса пользователя, имеющий стандартный внешний вид и выполняющий стандартные действия. Например, самый простой виджет Кнопка на него можно кликнуть, у него можно проверить текст и цвет. В Поле ввода можно ввести текст, проверить, какой текст введен, кликнуть, проверить отображение фокуса, проверить количество введенных символов, ввести текст и нажать Enter, проверить placeholder, проверить подсветку обязательности поля и текст ошибки, и всё, что еще может понадобиться в конкретном случае. При этом все действия с этим полем стандартны на любой странице.



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

  • Клик по элементу оглавления с заданным текстом,
  • Проверка существования элемента с заданным текстом,
  • Проверка отступа элемента с заданным текстом.


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

Для уменьшения всеобщего хаоса виджеты, как элементы страницы, объединяются во все те же пейджи, откуда, видимо, и составлено название Page Element.

public class UsersPage {    public Table usersTable = new Table();    public InputLine email = new InputLine(By.id("dataEmail"));    public InputLine password = new InputLine(By.id("dataPassword"));    public InputLine name = new InputLine(By.id("dataName"));    public DropdownList gender = new DropdownList(By.id("dataGender"));    public Checkbox var11 = new Checkbox(By.id("dataSelect11"));    public Checkbox var12 = new Checkbox(By.id("dataSelect12"));    public Checkbox var21 = new Checkbox(By.id("dataSelect21"));    public Checkbox var22 = new Checkbox(By.id("dataSelect22"));    public Checkbox var23 = new Checkbox(By.id("dataSelect23"));    public Button save = new Button(By.id("dataSend"));    public ErrorPopup errorPopup = new ErrorPopup();    public ModalPopup savePopup = new ModalPopup();}


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

    @Test    public void authAsAdmin() {        baseRouter                .authPage().email.fill("test@protei.ru")                .authPage().password.fill("test")                .authPage().enter.click()                .mainPage().logoutButton.shouldExist();    }


Можно добавить классический слой степов, если есть необходимость для этого в вашем фреймворке (реализация remote библиотеки на Java для RobotFramework требует на вход класс степов, например), или если хочется добавить аннотаций для красивых отчетов. Мы это сделали генератором на основе аннотаций, если интересно, пишите в комментах, расскажем.

Пример класса степов авторизации
public class AuthSteps{    private BaseRouter baseRouter = new BaseRouter();    @Step("Sigh in as {mail}")    public BaseSteps login(String mail, String password) {        baseRouter                .authPage().email.fill(mail)                .authPage().password.fill(password)                .authPage().enter.click()                .mainPage().logoutButton.shouldExist();        return this;    }    @Step("Fill E-mail")    public AuthSteps fillEmail(String email) {        baseRouter.authPage().email.fill(email);        return this;    }    @Step("Fill password")    public AuthSteps fillPassword(String password) {        baseRouter.authPage().password.fill(password);        return this;    }    @Step("Click enter")    public AuthSteps clickEnter() {        baseRouter.authPage().enter.click();        return this;    }    @Step("Enter should exist")    public AuthSteps shouldExistEnter() {        baseRouter.authPage().enter.shouldExist();        return this;    }    @Step("Logout")    public AuthSteps logout() {        baseRouter.mainPage().logoutButton.click()                .authPage().enter.shouldExist();        return this;    }}public class BaseRouter {// Класс для создания страниц, чтобы не дублировать этот код везде, где понадобится обращение к странице    public AuthPage authPage() {return page(AuthPage.class);}    public MainPage mainPage() {return page(MainPage.class);}    public UsersPage usersPage() {return page(UsersPage.class);}    public VariantsPage variantsPage() {return page(VariantsPage.class);}}



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

Таким образом, для составления теста нужно описать простые действия с каждым элементом страницы, причем некоторые элементы вроде полей ввода универсальны и могут быть использованы в нескольких проектах практически без изменений. После этого для каждой страницы указывается набор полей с локаторами. И все, можно писать сам тест, который гарантированно окажется читаемым и воспроизводимым, ведь он состоит из прямых указаний нажми тут, введи текст там. Проверили на десятке проектов этот подход самый читаемый, причем не надо дублировать в каждом page object однотипный код для каждого поля, ведь все нужные действия указаны один раз в виджетах!

Хранение данных


Неправильно говорить об архитектуре проекта, не затронув способы удобного оперирования тестовыми данными.

Самый простой способ передавать данные прямо в тесте как есть или переменными. Этот способ подходит для архитектуры простыни, но в больших проектах начинается бардак.

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

Для каждой сущности создается описывающая ее модель, которая в самом простом случае содержит названия и типы полей, например вот модель пользователя:

public class User {    private Integer id;    private String mail;    private String name;    private String password;    private Gender gender;    private boolean check11;    private boolean check12;    private boolean check21;    private boolean check22;    private boolean check23;    public enum Gender {        MALE,        FEMALE;        public String getVisibleText() {            switch (this) {                case MALE:                    return "Мужской";                case FEMALE:                    return "Женский";            }            return "";        }    }}


Лайфхак 1: если у вас rest-подобная архитектура клиент-серверного взаимодействия (между клиентом и сервером ходят json или xml объекты, а не кусочки нечитаемого кода), то можно загуглить json to <ваш язык> object, вероятно, нужный генератор уже есть.

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

Лайфхак 3: если вы джавист и компания позволяет использовать сторонние библиотеки, а вокруг нет нервных коллег, предсказывающих много боли еретикам, использующим дополнительные библиотеки вместо чистой и прекрасной Java, берите ломбок! Да, обычно IDE может сгенерировать геттеры, сеттеры, toString и билдеры. Но при сравнении наших ломбоковских моделек и разрабских без ломбока виден профит в сотни строк пустого, не несущего бизнес-логики кода на каждый класс. При использовании ломбока не надо бить по рукам тех, кто перемешивает поля и геттеры сеттеры, класс читается легче, можно получить представление об объекте сразу, без пролистывания трех экранов.

Таким образом, у нас появляются каркасы объектов, на которые нужно натянуть тестовые данные. Данные можно хранить как final static переменные, например это может быть полезно для главного админа системы, из под которого создаются другие пользователи. Использовать лучше именно final, чтобы не было соблазна данные изменять в тестах, потому что тогда следующий тест вместо админа может получить бесправного пользователя, не говоря уже про параллельный запуск тестов.

public class Users {    public static final User admin = User.builder().mail("test@protei.ru").password("test").build();}


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

    public static User getUserRandomData() {        User user = User.builder()                .mail(getRandomEmail())                .password(getShortLatinStr())                .name(getShortLatinStr())                .gender(getRandomFromEnum(User.Gender.class))                .check11(getRandomBool())                .check21(getRandomBool())                .check22(getRandomBool())                .check23(getRandomBool())                .build();//business-logic: 11 xor 12 must be selected        if (!user.isCheck11()) user.setCheck12(true);         if (user.isCheck11()) user.setCheck12(false);        return user;    }


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



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

Этот способ хранения данных использует паттерн Value Object, на его основе можно добавить любых своих хотелок, в зависимости от надобностей проекта. Можно добавить сохранение объектов в базу, и таким образом подготовить систему перед тестом. Можно не рандомить пользователей, а загружать их из файлов properties (и еще одна классная библиотека ). Можно использовать везде одного и того же пользователя, но сделать так называемые Реестры данных (data registry) под каждый вид объектов, в котором к имени или другому уникальному полю объекта будет добавляться значение сквозного счетчика, и в тесте всегда будет свой уникальный testUser_135.

Можно написать свое Хранилище объектов (гуглить object pool и flyweight), из которого запрашивать необходимые сущности в начале теста. Хранилище отдает один из своих уже готовых к работе объектов и отмечает его у себя занятым. В конце теста объект возвращается в хранилище, где его по необходимости чистят, отмечают свободным и отдают следующему тесту. Так делают, если операции создания объектов очень ресурсоемкие, а при таком подходе хранилище работает независимо от тестов и может заниматься подготовкой данных под следующие кейсы.

Создание данных


Для кейсов редактирования пользователя вам определенно понадобится созданный пользователь, которого вы будете редактировать, при этом, в общем-то, тесту редактирования не важно, откуда этот пользователь взялся. Есть несколько способов его создать:

  • нажимать кнопки руками перед тестом,
  • оставить данные от предыдущего теста,
  • развернуть перед тестом из backup,
  • создать кликами по кнопкам прямо в тесте,
  • использовать API.


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

Использование результатов предыдущего теста нарушает принцип атомарности и не дает запускать тест отдельно, придется запускать всю пачку, а ui-тесты не такие уж и быстрые. Хорошим тоном считается писать тесты так, что каждый можно запустить в гордом одиночестве и без дополнительных танцев. Кроме того, бага в создании объекта, уронившая предыдущий тест, вовсе не гарантирует багу в редактировании, а в такой конструкции тест на редактирование упадет следом, и работает ли именно редактирование не узнать.

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

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

Наконец, еще один способ создания пользователя через http-API из теста, то есть вместо кликов по кнопкам сразу отправить запрос на создание нужного пользователя. Таким образом уменьшен, насколько возможно, пестицид, очевидно, откуда взялся пользователь, а скорость создания сильно выше, чем при кликах по кнопкам. Минусы этого способа в том, что он не подходит для проектов без json или xml в протоколе обмена данными между клиентом и сервером (например если разработчики пишут используя gwt и не хотят писать дополнительный api для тестировщиков). Можно при использовании API потерять кусок логики, выполняемой админкой, и создать не валидную сущность. API может меняться, отчего тесты упадут, однако обычно об этом известно, да и изменения ради изменений никому не нужны, скорее всего это новая логика, которую все равно придется проверять. Также возможно, что и на уровне API будет бага, но от этого ни один способ кроме готовых backup не застрахован, поэтому подходы к созданию данных лучше комбинировать.

Добавим капельку API


Среди способов подготовки данных нам больше всего подошли http-API для текущих нужд отдельного теста и разворачивание backup для дополнительных тестовых данных, которые в тестах не меняются, например, иконок для объектов, чтобы тесты этих объектов не падали при багах в загрузке иконок.

Для создания объектов через API в Java оказалось удобнее всего использовать библиотеку restAssured, хоть она предназначена не совсем для этого. Хочу поделиться парой найденных фишек, знаете еще пишите!

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

public class ApiSettings {    private static String loginEndpoint="/login";    public static RequestSpecification testApi() {        RequestSpecBuilder tmp = new RequestSpecBuilder()                .setBaseUri(testConfig.getSiteUrl())                .setContentType(ContentType.JSON)                .setAccept(ContentType.JSON)                .addFilter(new BeautifulRest())                .log(LogDetail.ALL);        Map<String, String> cookies = RestAssured.given().spec(tmp.build())                .body(admin)                .post(loginEndpoint).then().statusCode(200).extract().cookies();        return tmp.addCookies(cookies).build();    }}


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

Есть плюшечка для аллюра и красивых отчетов, обратите внимание на строку .addFilter(new BeautifulRest()):

Класс BeautifulRest

public class BeautifulRest extends AllureRestAssured {        public BeautifulRest() {}        public Response filter(FilterableRequestSpecification requestSpec, FilterableResponseSpecification responseSpec, FilterContext filterContext) {            AllureLifecycle lifecycle = Allure.getLifecycle();            lifecycle.startStep(UUID.randomUUID().toString(), (new StepResult()).setStatus(Status.PASSED).setName(String.format("%s: %s", requestSpec.getMethod(), requestSpec.getURI())));            Response response;            try {                response = super.filter(requestSpec, responseSpec, filterContext);            } finally {                lifecycle.stopStep();            }            return response;        }}



Модели объектов отлично ложатся на restAssured, так как библиотека сама справляется с сериализацией и десериализаций моделей в json/xml (превращением из json/xml форматов в объект заданного класса).

    @Step("create user")    public static User createUser(User user) {        String usersEndpoint = "/user";        return RestAssured.given().spec(ApiSettings.testApi())                .when()                .body(user)                .post(usersEndpoint)                .then().log().all()                .statusCode(200)                .body("state",containsString("OK"))                .extract().as(User.class);    }


Если рассмотреть подряд несколько степов создания объектов, можно заметить идентичность кода. Для уменьшения одинакового кода можно написать общий метод создания объектов.

    public static Object create(String endpoint, Object model) {        return RestAssured.given().spec(ApiSettings.testApi())                .when()                .body(model)                .post(endpoint)                .then().log().all()                .statusCode(200)                .body("state",containsString("OK"))                .extract().as(model.getClass());    }    @Step("create user")    public static User createUser(User user) {                  create(User.endpoint, user);    }


Еще раз про рутинные операции


В рамках проверки редактирования объекта нам в целом не важно, как объект появился в системе через api или из backup, или все-таки создан ui-тестом. Важные действия найти объект, нажать у него иконку редактировать, очистить поля и заполнить их новыми значениями, нажать сохранить и проверить, все ли новые значения правильно сохранились. Всю ненужную информацию, не относящуюся непосредственно к тесту, лучше убирать в отдельные методы, например, в класс степов.

    @Test    void checkUserVars() {        //Arrange        User userForTest = getUserRandomData();        // Проверка корректности сохранения полей уже есть в другом тесте,  // этот тест проверяет отображение вариантов из-под залогинившегося юзера,  // поэтому не важно, как юзер создан        usersSteps.createUser(userForTest);        authSteps.login(userForTest);        //Act        mainMenuSteps                .clickVariantsMenu();        //Assert        variantsSteps                .checkAllVariantsArePresent(userForTest.getVars())                .checkVariantsCount(userForTest.getVarsCount());        //Cleanup        usersSteps.deleteUser(userForTest);    }


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

    @Test    void authAsAdmin() {        authSteps.login(Users.admin);// Это всё, просто авторизовались под админом. Все действия и проверки внутри. // Не очень очевидно, не правда ли? 


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

Вместо этого нужно почитать про data-driven тесты, для Java+TestNG это будет примерно так:

    @Test(dataProvider = "usersWithDifferentVars")    void checkUserDifferentVars(User userForTest) {        //Arrange        usersSteps.createUser(userForTest);        authSteps.login(userForTest);        //Act        mainMenuSteps                .clickVariantsMenu();        //Assert        variantsSteps                .checkAllVariantsArePresent(userForTest.getVars())                .checkVariantsCount(userForTest.getVarsCount());    } // Метод возвращает пользователей с полным перебором трех булевых параметров.  // Предположим, это важное бизнес-требование.    @DataSupplier(name = "usersWithDifferentVars")    public Stream<User> usersWithDifferentVars(){        return Stream.of(            getUserRandomData().setCheck21(false).setCheck22(false).setCheck23(false),            getUserRandomData().setCheck21(true).setCheck22(false).setCheck23(false),            getUserRandomData().setCheck21(false).setCheck22(true).setCheck23(false),            getUserRandomData().setCheck21(false).setCheck22(false).setCheck23(true),            getUserRandomData().setCheck21(true).setCheck22(true).setCheck23(false),            getUserRandomData().setCheck21(true).setCheck22(false).setCheck23(true),            getUserRandomData().setCheck21(false).setCheck22(true).setCheck23(true),            getUserRandomData().setCheck21(true).setCheck22(true).setCheck23(true)        );    }


Тут используется библиотека Data Supplier, которая является надстройкой над TestNG Data Provider, позволяющей использовать типизированные коллекции вместо Object [] [], но суть та же. Таким образом мы получаем один тест, выполняемый столько раз, сколько входных данных он получает.

Выводы


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

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

Примеры кода из статьи в виде готового проекта добавлены в гит.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru