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

Testing

AEM Test Automation Create Pages via HTTP Requests

28.04.2021 20:06:12 | Автор: admin

Если вы уже знакомы с АЕМ-ом, смело пропускайте эту часть. Если же нет - вам стоит понять, что же такое этот АЕМ и почему с его тестированием возникают сложности.

AEM - content management system от Adobe (как выразилась коллега - WordPress на стероидах). Что это значит? Мы не создаем непосредственные веб страницы, а работаем над сложной админкой (AEM-author), которая в будущем позволит контенщикам (Editors) создавать эти страницы, используя набор определенных компонентов. Эти страницы будут видны (после publish действия) конечным юзерам (AEM-publish). Так что наша работа, собственно, и заключается в создании этих компонент.

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

И так, что мы имели AEM version 6.4.4.0.Процессы тестирования, установленные задолго до нашего привлечения. Вся ответственность была возложена на автотесты:

  1. Screenshots tests Поскольку AEM content management system => Значит наши стили, да и вообще весь фронт очень важен, ведь это то с чем сталкивается конечный пользователь (возможно расскажу об этом в другой статье).

  2. Web-component tests самые обычные UI тесты с использованием Cypress в качестве основы. Только проверялись не страницы, а компоненты.

  3. Web Performance tests мониторинг производительности наших Web-страниц (с помощью Sitespeed)

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

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

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

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

И так какой же выход?

А что если мы будем создавать страницы на лету?

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

Long story short

К сожалению, АЕМ не предоставляет никакой информации о своем API. А если такая информация где-то и есть, я не смог ее найти. Да и тестирование АЕМ-а минимально описанная головная боль (Тестируете АЕМ? Делитесь в комментариях как).

Так что последующие выводы целиком и полностью reverse engineering построенный на запросах отправленных фронтом (AEM-author) на бек.

В моем случае, и я уверен в подавляющем большинстве случаев, - создание страниц в АЕМ-е сведется к следующим действиям:

  1. Создание страницы с помощью темплейта (page template)

  2. Добавление компонент на страницу

  3. Конфигурация компонент

  4. Паблиш страницы

    И, конечно, мы будем:

  5. Удалять страницы


Приступим...

Нам понадобиться dev-tools на вкладке Network.

1. Создание страницы с помощью темплейта

  1. Открываем AEM-author

  2. Sites

  3. Переходим в нужную нам папку

  4. Жмякаем Create

  5. Выбираем нужный нам template

  6. Заполняем интересующие нас поля

  7. Запускаем запись Network запросов в dev-tools

  8. Нажимаем Create

Первый же запрос `${aem-author-URL}/libs/wcm/core/content/sites/createpagewizard/_jcr_content` будет содержать всю необходимую нам информацию.

Тут мы сталкиваемся с первой сложностью. Это POST запрос с FormData. Т.е. запрос не содержит привычный многим тестировщикам body в виде JSON/XML объекта. Вместо этого данные отправляются как Content-Type: application/x-www-form-urlencoded.

Во многих случаях любой JS объект можно легко перевести в данный формат (по-сути, это будет простая url encoded строка key=value записанная через &). Правда, тут нужно быть осторожными, поскольку данный формат не подразумевает, что ключ (key) является уникальным. Т.е. ваша Form Data может содержать tags=Tag1&tags=Tag2 (несколько тегов у страницы, в моем случае).

[Request Example] URL encoded FormData[Request Example] URL encoded FormData[Request Example] Parsed FormData[Request Example] Parsed FormData

Самое важное на что стоит обратить внимание на этих скриншотах:

  • parentPath папка/страницв в AEM-e, в которой будет создана наша страница

  • template путь к теплейту, который будет использован для создания страницы

Ниже идут поля, применимые к моему проекту

  • ./jcr:title имя/тайтл моей страницы (обязательное поле на UI)

  • ./cq:tags тег, который я добавил странице (опциональное поле)

  • ./articleDate, ./articleTimeToRead и :cq_csrf_token

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

Теперь, на счет авторизации. Как видно выше, запросы содержат token. Я использовал cypress.io для написания автотестов, так что для авторизации API запросов просто указывал auth объект с username и password, на ряду с методом, хедером и body. (For more info check Cypress: Request - arguments and http-authentication).

Реализация отправки POST запроса с FormDataРеализация отправки POST запроса с FormData

key takeaways: все запросы на создание новых страниц идут на `${aem-author-URL}/libs/wcm/core/content/sites/createpagewizard/_jcr_content`, parentPath и template присутствуют для всех вариантов создания страниц с помощью темплейта.


2. Добавление компонент на страницу

  1. Находим нужную нам страницу в AEM author

  2. Жмякаем Edit

  3. Выбираем нужную позицию (место) для компоненты

  4. Жмякаем +

  5. Запускаем запись Network запросов в dev-tools

  6. Находим и выбираем нужный нам компонент

Нужный нам запрос `${aem-author-URL}/content/${page-path}/jcr:content/par/${some-url-part}/par/`.

[Request Example] Add Component to the page[Request Example] Add Component to the page

Из важного тут:

  • ./@CopyFrom темплейт (default) конфигурации компоненты (button в моем случае)

  • ./sling:resourceType название и путь к компоненте, которую я добавлял

  • parentResourceType тут я не уверен, судя по всему место, куда добавить компоненту


3. Конфигурация компонент

  1. На нужной нам странице выбираем необходимый компонент

  2. Жмякаем гаечный ключ

  3. Модифицируем конфигурацию данной компоненты

  4. Запускаем запись Network запросов в dev-tools

  5. Жмякаем кнопку Done

Наш запрос первый в списке `${aem-author-URL}/content/${page-path}/_jcr_content/par/${component-name}`.

[Request Example] Configure Component[Request Example] Configure Component

Из важного тут:

  • ./sling:resourceType название и путь к компоненте, которую я добавлял

  • :cq_csrf_token токен, значит нужно использовать auth


4. Паблиш страницы

  1. Находим нужную нам страницу

  2. Запускаем запись Network запросов в dev-tools

  3. Жмякаем Quick Publish -> Publish

4.1.

В данном случае нам нужны первые 2 запроса.

4.1.1. Получение связанных ассетов

Запрос reference.json `${aem-author-URL}/libs/wcm/core/content/reference.json?${url-params}` получаем информацию о ассетах (assets), cвязаных с нашей страницей.

[Request Example] Check Assets related to the published page[Request Example] Check Assets related to the published page

Из важного тут используются query string params. В path указан путь к нашей странице.

В ответе нам придет массив ассетов. Нам понадобятся path`s тех, чей published статус false.

[Responce Example] Check Assets[Responce Example] Check Assets

4.1.2. Публикация страницы и ассетов

Запрос replicate `${aem-author-URL}/bin/replicate` запрос на публикацию нашей страницы и связанных с ней сущностей.

[Request Example] Publish Page and Related Assets[Request Example] Publish Page and Related Assets

Как вы видите, основное на что стоит обратить внимание:

  • cmd: Activate команда для паблиша страницы

  • path пути к нашей странице и к 2м ассетам

4.2.

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

Для этого нам понадобиться еще один запрос GET `${aem-author-URL}/etc/replication/agents.author/publish_publish/jcr:content.queue.json`. Он вернет нам массив страниц ожидающихпаблишинга. Так что придётся сделать, как минимум еще один запрос, проверить есть ли необходимая нам страница в массивеbody.queue.Опять же искать по path. Если страница все еще присутствует в очереди, придётся повторить проверку один или даже несколько раз (я выставил timeout в 1 секунду, но думаю можно и меньше).


5. Удаление страницы

  1. Находим нужную нам страницу

  2. Выбираем ее

  3. Запускаем запись Network запросов в dev-tools

  4. Жмякаем Delete-> Delete

Наш запрос `${aem-author-URL}/bin/wcmcommand`.

[Request Example] Delete Page[Request Example] Delete Page

Из важного тут:

  • cmd deletePage

  • path путь к странице

  • force: false но я бы рекомендовал ставить true (дабы при удалении не происходило дополнительных проверок)

  • checkChildren: true можно опустить


Итак, подведем итог

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

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

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

  • AEM заменяет пробелы на `-`. То есть если вы создали страницу с Тайтлом `Bla 1 2 3 4` и не указали специфический путь к ней, тогда АЕМ сделает эту страницу доступной по пути /bla-1-2-3-4

  • Пути страниц всегда будут в lowerCase (см пред. пример)

  • При использовании `_` в тайтле начиная с (приблизительно с 18и символов) АЕМ удалит все последующие `_`. Те если вы создали страницу с тайтлом BlaBla123456789123456_blabla, то доступ к ней будет не по /blabla123456789123456_blabla, а по /blabla123456789123456blabla

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

Подробнее..

WebRTC CDN на Google Cloud Platform с балансировкой и автоматическим масштабированием

18.06.2021 10:22:12 | Автор: admin

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

Кратко напомним основные тезисы:

  • В CDN низкая задержка в трансляциях обеспечивается использованием технологии WebRTC для передачи видеопотока от Origin сервера к Edge серверам, которые, в свою очередь, позволяют подключить большое количество зрителей.

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

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

Инфраструктура Google Cloud Platform, как и в случае с AWS, поддерживает автоматическое масштабирование и балансировку распределения нагрузки, поэтому не приходится беспокоиться о лишних расходах на оплату виртуальных серверов вы платите только за то, что используете.

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

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

Разворачиваем WebRTC CDN с балансировщиком и автоматическим масштабированием на Google Cloud Platform

Конфигурация CDN будет следующей:

  • один Origin сервер;

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

Для развертывания потребуется настроить следующие компоненты в консоли Google Cloud Platform:

  • глобальный файрволл на уровне проекта Google Cloud;

  • виртуальные машины WCS CDN Origin и WCS CDN Edge;

  • шаблон развертывания на основе образа диска WCS CDN Edge;

  • группу масштабирования;

  • балансировщик нагрузки.

Итак, приступим.

Настраиваем глобальный файрволл на уровне проекта Google Cloud для прохождения WebRTC трафика

Настройка межсетевого экрана действует на все запущенные в вашем проекте сервера, поэтому начнем развертывание с нее.

В основном меню консоли Google Cloud откройте раздел "VPC networks" и выберите пункт "Firewall":

На открывшейся странице нажмите кнопку "Create Firewall Rule" :

В открывшемся мастере задайте имя правила:

Ниже на странице разрешите входящий трафик с любых компьютеров:

Еще ниже в секции "Protocols and ports" укажите порты для работы WCS и нажмите кнопку "Create":

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

Разворачиваем WCS сервер с ролью Origin для WebRTC CDN

В консоли Google Cloud откройте раздел "Compute Engine" и выберите из меню в левой части пункт "VM instances". Нажмите кнопку "Create" в диалоге создания нового экземпляра сервера:

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

Ниже на странице в секции "Boot disk" нажмите кнопку "Change" и выберите образ "CentOS 7":

Разверните секцию "Management, security, disks, networking, sole tenancy":

На вкладке "Security" добавьте публичный ключ для доступа к серверу по SSH:

На вкладке "Networking" в секции "Network interfaces" настройте внешний и внутренний IP адреса для сервера. Для работы в составе CDN серверу нужно назначить статический внутренний IP адрес:

После всех настроек нажмите кнопку "Create" для создания нового экземпляра WCS сервера с ролью CDN Origin:

Спустя пару минут сервер будет создан и запущен. Подключаемся к нему по ssh и устанавливаем WCS. Все действия - установка, изменение настроек, запуск или перезапуск WCS - должны выполняться с root правами, либо через sudo.

1.Установите Wget, Midnight Commander и дополнительные инструменты и библиотеки

sudo yum -y install wget mc tcpdump iperf3 fontconfig

2.Установите JDK. Для работы в условиях больших нагрузок рекомендуется JDK 12 или 14. Удобнее провести установку при помощи скрипта на bash. Текст скрипта:

#!/bin/bashsudo rm -rf jdk*curl -s https://download.java.net/java/GA/jdk12.0.2/e482c34c86bd4bf8b56c0b35558996b9/10/GPL/openjdk-12.0.2_linux-x64_bin.tar.gz | tar -zx[ ! -d jdk-12.0.2/bin ] && exit 1sudo mkdir -p /usr/java[ -d /usr/java/jdk-12.0.2 ] && sudo rm -rf /usr/java/jdk-12.0.2sudo mv -f jdk-12.0.2 /usr/java[ ! -d /usr/java/jdk-12.0.2/bin ] && exit 1sudo rm -f /usr/java/defaultsudo ln -sf /usr/java/jdk-12.0.2 /usr/java/defaultsudo update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-12.0.2/bin/java" 1sudo update-alternatives --install "/usr/bin/jstack" "jstack" "/usr/java/jdk-12.0.2/bin/jstack" 1sudo update-alternatives --install "/usr/bin/jcmd" "jcmd" "/usr/java/jdk-12.0.2/bin/jcmd" 1sudo update-alternatives --install "/usr/bin/jmap" "jmap" "/usr/java/jdk-12.0.2/bin/jmap" 1sudo update-alternatives --set "java" "/usr/java/jdk-12.0.2/bin/java"sudo update-alternatives --set "jstack" "/usr/java/jdk-12.0.2/bin/jstack"sudo update-alternatives --set "jcmd" "/usr/java/jdk-12.0.2/bin/jcmd"sudo update-alternatives --set "jmap" "/usr/java/jdk-12.0.2/bin/jmap"

3.Загрузите архив для установки самой свежей стабильной версии WebCallServer:

sudo wget https://flashphoner.com/download-wcs5.2-server.tar.gz

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

sudo tar -xvzf FlashphonerWebCallServer-5.2.714.tar.gz && cd FlashphonerWebCallServer-5.2.714 && ./install.sh

5.Для активации лицензии запустите скрипт "./activation.sh" из каталога установки WCS. Этот шаг, при желании, можно пропустить и активировать лицензию позже через веб-интерфейс:

sudo cd /usr/local/FlashphonerWebCallServer/bin && sudo ./activation.sh

6.Отключите firewalld и SELinux. Сетевой экран мы ранее настроили на уровне Google Cloud Platform, поэтому нет необходимости закрывать порты в операционной системе:

sudo systemctl stop firewalld && systemctl disable firewalld && setenforce 0

7.Откройте любым удобным редактором файл flashphoner.properties, который можно найти по пути:

/usr/local/FlashphonerWebCallServer/conf/flashphoner.properties

и внесите в него настройки для запуска CDN. В параметре "cdn_ip" укажите внутренний IP адрес вашей виртуальной машины с ролью CDN Origin:

cdn_enabled=truecdn_ip=10.128.0.3 # Local IP address CDN Origincdn_nodes_resolve_ip=falsecdn_role=origin

На скриншоте ниже примерный вид файла flashphoner.properties для WCS с ролью CDN Origin:

После изменения настроек запустите (или перезапустите) Web Call Server:

systemctl start webcallserver

На этом запуск и настройка Origin закончены. Перейдем к настройке балансировщика нагрузки и автоматического масштабирования.

Запускаем балансировщик нагрузки и автоматическое масштабирование в Google Cloud для WebRTC CDN

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

  • образ диска, который будет использоваться в шаблоне при создании нового экземпляра WCS;

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

  • группа масштабирования;

  • балансировщик нагрузки;

  • настройки контроля активности сервера.

Создаем образ диска WCS сервера с ролью Edge для WebRTC CDN

Начнем настройку с развертывания WCS сервера с ролью CDN Edge, для того, что бы создать на его основе образ диска для шаблона создания новых экземпляров WCS в балансировщике.

Повторите инструкцию по подготовке сервера Origin до пункта о внесении настроек в файл flashphoner.properties. Для роли Edge внесите в этот файл следующие настройки:

cdn_enabled=truecdn_ip=10.128.0.4cdn_nodes_resolve_ip=falsecdn_point_of_entry=10.128.0.3cdn_role=edgehttp_enable_root_redirect=false

После внесения и сохранения настроек, остановите в консоли Google Cloud виртуальную машину WCS CDN Edge, выберите из меню в левой части пункт "Images" и нажмите кнопку "Create Image":

В открывшемся мастере укажите имя нового образа, выберите в качестве источника диск виртуальной машины WCS CDN Edge и нажмите кнопку "Create":

После того, как образ диска создан переходим к созданию шаблона развертывания Edge сервера на основе созданного образа.

Создаем шаблон развертывания Edge сервера

Выберите из меню в левой части окна консоли Google Cloud пункт "Instance templates" и нажмите кнопку "Create Instance template":

В открывшемся мастере создания шаблона развертывания укажите имя шаблона и выберите конфигурацию виртуальной машины:

Ниже на странице в секции "Boot disk" нажмите кнопку "Change". в Открывшемся окне перейдите на вкладку "Custom Images" и выберите образ диска WCS CDN Edge, который мы создали ранее. Нажмите кнопку "Select":

Разверните секцию "Management, security, disks, networking, sole tenancy". На вкладке "Security" добавьте публичный ключ для доступа к серверу по SSH и нажмите кнопку "Create":

Шаблон развертывания для WCS с ролью CDN Edge создан. Теперь перейдем к созданию группы масштабирования.

Создаем группы масштабирования для Edge серверов

Из меню в левой части окна консоли Google Cloud выберите пункт "Instance groups" и нажмите кнопку "Create Instance group":

На открывшейся странице выберите регион и зону расположения группы и укажите шаблон развертывания WCS Edge, который мы создали ранее:

В секции "Autoscaling" на этой же странице настройте триггер запуска дополнительных серверов Edge. В качестве триггера будем использовать загрузку процессора более 80% . В поле "Maximum number of instances" укажите максимальное количество виртуальных машин, которые будут запущены при срабатывании триггера:

Затем включите проверку состояния виртуальной машины в секции "Autohealing". Для того, что бы создать настройку проверки сервера выберите из списка в поле "Health check" пункт "Сreate a health check":

В открывшемся мастере создания проверки состояния сервера укажите имя проверки, протокол TCP, порт 8081 и запрос /health-check. Настройте критерии проверки и нажмите кнопку "Save and continue":

Разверните секцию "Advanced creation options" и активируйте чекбокс "Do not retry machine creation". После чего нажмите "Create":

Будет создана группа масштабирования и запущен один WCS с ролью CDN Edge. Последним этапом настройки нашей CDN с балансировщиком нагрузки и автоматическим масштабированием будет настройка балансировщика.

Создаем балансировщик нагрузки

Сначала зарезервируем для балансировщика внешний IP адрес. В главном меню Google Cloud Platform в секции "VPC network" выберите пункт "External IP addresses" и нажмите кнопку "Reserve static address":

На открывшейся странице в поле "Name" задаем имя для зарезервированного IP адреса. Выбираем уровень качества сетевых услуг для адреса и тип распространения. После завершения всех настроек нажимаем кнопку "Reserve":

Затем переходим к настройке балансировщика.

Выбираем пункт "Load balancing" в разделе "Network services" секции "Networking" основного меню Google Cloud Platform:

Нажимаем кнопку "Create load balancer":

Затем выберите тип балансировщика "TCP Load Balancing" и нажмите кнопку "Start configuration":

На открывшейся странице укажите внешний балансировщик "From Internet to my VMs" и регион размещения серверов балансировщика. После выбора настроек нажмите кнопку "Continue":

На следующей странице задайте имя балансировщика, Затем перейдите в раздел настроек "Backend configuration" и укажите в каком регионе будут созданы сервера входящие в состав балансировщика. На вкладке "Select existing instance groups" выберите группу масштабирования Edge серверов, которую мы создали ранее. Затем в поле "Health check"выберите из выпадающего списка пункт "Сreate a health check":

На открывшейся странице укажите параметры для проверки состояния работы балансировщика порт 8081 и запрос /, после чего нажмите кнопку "Save and continue":

Затем перейдите к настройкам раздела "Frontend configuration". В этом разделе нужно создать привязку портов к внешнему IP адресу. Укажите внешний IP адрес для балансировщика, который мы зарезервировали выше и создайте конфигурации для TCP портов 8081, 8080, 8443, 8444 для HTTP(S) и WS(S). После создания необходимых портов нажмите кнопку "Create":

Балансировщик будет запущен. На этом развертывание CDN с балансировщиком и масштабированием можно считать завершенным. Переходим к тестированию.

Тестирование WebRTC CDN с балансировщиком и масштабированием на базе Google Cloud Platform

Методика тестирования

Для проведения нагрузочного тестирования, при создании группы масштабирования мы выставили порог загрузки процессора для срабатывания триггера на 20%. Тестирование будем проводить с использованием браузера Google Chrome и виртуальной вебкамеры для организации трансляции видеопотока. Что бы сымитировать повышение нагрузки на процессор запустим воспроизведение потока с транскодированием с помощью примера "Media Devices".

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

Тестирование

В браузере Google Chrome открываем web интерфейс WCS с ролью CDN Origin Авторизуемся, открываем пример "Two-way Streaming", устанавливаем соединение с сервером по WebSocket и публикуем видеопоток.

Затем, запускаем web интерфейс WCS CDN Edge сервера по IP адресу, который был зарезервирован при создании балансировщика.

Авторизуемся, открываем пример "Media Devices" и устанавливаем соединение с балансировщиком по WebSocket. В правом столбце настроек снимаем чек бокс "default" для параметра "Size" и задаем значения для транскодирования видеопотока. Например, если поток на Origin сервере опубликован с размерами 320х240 задаем значение 640х480. Повторите действия в нескольких вкладках браузера, для имитации большого количества зрителей.

В консоли Google Cloud видим, что были запущены две дополнительные виртуальные машины:

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

http://<WCS instance IP address>:8081/?action=stat

Откройте страницу статистики для каждой виртуальной машины, запущенной балансировщиком. Значение "connection_websocket" показывает количество активных WebSocket сессий на каждой виртуальной машине.

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

Хорошего стриминга!

Ссылки

Наш демо сервер

CDN для стриминга WebRTC с низкой задержкой - CDN на базе WCS

Документация по быстрому развертыванию и тестированию WCS сервера

Документация по развертыванию WCS в Google Cloud Platform

Документация по настройке балансировки нагрузки с масштабированием в GCP

Подробнее..

VirtualBox Запуск Android эмулятора в виртуальной среде для тестирования Android проекта

19.12.2020 00:19:44 | Автор: admin

Введение

В данной статье я постараюсь описать пример инфраструктуры для автотестов Android приложений (mobile automation), а именно, среду для проведения тестранов UI автотестов на эмуляторе Android девайса в виртуальной среде.

Требования:

Для Android эмулятора нужна поддержка Intel Virtualization Technology или AMD Virtualization. Поэтому часто тестировщик сталкивается с необходимостью запуска тестранов только в нативной среде ПК с прямым доступом к центральному процессору.

В этом случае схема получается такая:

Трудности:

  1. Невозможно легко пересоздать среду эмулятора.

  2. Среда не создаётся перед проведением тестирования, и после проведения не удаляется, поэтому среда может влиять на тестируемое приложение.

  3. Починка и настройка среды занимает много времени.

Предлагаемое решение в данной статье:

  1. Создать VM с использованием возможностей nested virtualization VirtualBox (более подробное описание технологии в этой статье).

  2. Пробросить поддержку Intel-VT или KVM внутрь созданной виртуальной машины.

  3. Изнутри VM создать и запустить Android эмулятор девайса.

  4. Провести тестран UI тестов приложения.

  5. После проведения тестирования уничтожить VM.

В этом случае схема получится такая:

Предполагаемые преимущества:

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

  2. Уменьшится время поддержки среды и управляющего ПО, так как не нужно каждый раз руками ничего устанавливать и чинить неисправности инвайронмента.

В настоящей статье будет использоваться оборудование:

  • процеcсор: Intel i5-1035G1

  • память: 12Gb

  • в BIOS включена поддержка виртуализации процессора

  • OC: Ubuntu 20.4

Шаг 1: Установка ПО на нативную OS

Отдельно обращу внимание на управление машиной. Будем использовать протокол VNC для создания удобного удаленного рабочего стола. Протокол универсальный, для Linux, Windows, Mac и т.д.

x11vnc сервер

Установка:

sudo apt-get update #обновляем пакетыsudo apt install x11vnc #устанавливаем x11vncsudo x11vnc -storepasswd <вводим пароль сервера> /etc/x11vnc.pass #создаём пароль в файликеsudo chmod ugo+r /etc/x11vnc.pass #разрешаем использовать файлик с паролем

Запуск с параметрами:

x11vnc -nevershared -forever -dontdisconnect -many -noxfixes -rfbauth /etc/x11vnc.pass

Установка VirtualBox

Вводим в командной строке:

sudo apt-get updatesudo apt install gcc make linux-headers-$(uname -r) dkmswget -q https://www.virtualbox.org/download/oracle_vbox_2016.asc -O- | sudo apt-key add -wget -q https://www.virtualbox.org/download/oracle_vbox.asc -O- | sudo apt-key add -sudo sh -c 'echo "deb http://download.virtualbox.org/virtualbox/debian $(lsb_release -sc) contrib" >> /etc/apt/sources.list.d/virtualbox.list'sudo apt update #обновляем репозиторийsudo apt install virtualbox-6.1

Создание VM

Мы пойдем по самому простому пути и создадим VM из интерфейса VirtualBox с такими характеристиками. В дальнейшем создание VM будет code-first

  • Количество CPU - не больше половины имеющихся на Вашем процессоре (в идеале половина)

  • Оперативная память - будет достаточно 4Gb

Nested Virtualization можно также включить из командной строки:

VBoxManage modifyvm <Имя VM> --nested-hw-virt on

Далее переходим в саму VM.

Шаг 2: Установка ПО на VM

В первый раз мы установим всё руками. В дальнейшем весь установочный сценарий будет помещен в Packer, что позволит нам создавать VM с нужными настройками каждый раз перед началом тестирования.

Устанавливаем последний образ Ubuntu с официального сайта.

Установка KVM

egrep -c '(vmx|svm)' /proc/cpuinfo #Если в результате будет возвращено 0 - значит Ваш процессор не поддерживает аппаратной виртуализации, если 1 или больше - то вы можете использовать KVM на своей машинеsudo apt-get update #Обновляем пакетыsudo apt install qemu qemu-kvm libvirt-daemon libvirt-clients bridge-utils virt-manager #Установка KVM и сопроводительные либыsudo usermod -G libvirt -a ubuntu #Добавление пользователя ubuntu в группу libvirtsudo systemctl status libvirtd #Проверка запуска сервиса libvirtsudo kvm-ok #Проверка статуса KVM

Установка Android command line tools

sudo apt-get update #обновляем пакетыyes | sudo apt install android-sdk #устанавливаем Android SDKsudo apt install unzip #Устанавливаем unzip для распаковки архивовcd ~/Downloads #переходим в каталог Downloadswget https://dl.google.com/android/repository/commandlinetools-linux-6858069_latest.zip #скачиваем архив с command line tools с официального сайта Googlesudo unzip commandlinetools-linux-6858069_latest.zip -d /usr/lib/android-sdk/cmdline-tools/ #распаковываемsudo mv /usr/lib/android-sdk/cmdline-tools/cmdline-tools /usr/lib/android-sdk/cmdline-tools/tools #переименовываем каталог с тулами. Сейчас странная ситуация, Google раздаёт тулу с одним каталогом, а SDK ищет его в другом каталогеexport ANDROID_SDK_ROOT=/usr/lib/android-sdk #регистируем переменнуюexport PATH=$PATH:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/cmdline-tools/tools/bin #регистрируем новый Pathexport PATH=$PATH:$ANDROID_SDK_ROOT/emulator #регистируем новый Path

Проверяем, что sdkmanager работает и Android SDK доступен:

sdkmanager --version

Устанавливаем Android tools

yes | sdkmanager --licenses #принимаем лицензииsudo chown $USER:$USER $ANDROID_SDK_ROOT -R #Ставим для текущего юзера право менять содержимое папки с ANDROID_SDK_ROOTyes | sdkmanager "cmdline-tools;latest" #устанавливаем cmdline-toolssdkmanager "build-tools;30.0.3" #Устанавливаем build-toolssdkmanager "platform-tools" #Устанавливаем platform-toolssdkmanager "platforms;android-30"sdkmanager "sources;android-30"sdkmanager "emulator" #Устанавливаем AVD manageremulator -accel-check #Проверяем, есть ли поддержка виртуализацииyes | sdkmanager "system-images;android-23;google_apis;x86_64" #Устанавливаем образ для эмулятораsdkmanager --list #Выводим список установленных пакетов. Обычно для CI оставляю.no | avdmanager create avd -n android-23_google_apis_x86_64 -k "system-images;android-23;google_apis;x86_64" #создаём эмулятор из образаemulator -list-avds #проверяем наличие созданного эмулятора

Устанавливаем Git и клонируем проект

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

sudo apt update #обновляем пакетыyes | sudo apt install git #установка Gitgit --version #проверка установкиmkdir ~/workspace #создаём каталог для проектовcd ~/workspace #переходим в каталог для проектовgit clone https://github.com/panarik/AndroidClearApp.git #клонируем проект на локалcd ~/workspace/AndroidClearApp #переходим в каталог проекта

Шаг 3: Проведение тестирования проекта на созданном Android эмуляторе

./gradlew assembleDebug --no-daemon #билдим APKemulator -avd android-23_google_apis_x86_64 -no-audio -no-window -verbose -gpu off -accel off #запускаем эмулятор из ранее созданныхsleep 240 #аналог будильника, ждём четыре минуты пока загрузится эмуляторadb get-state #проверяем, видит ли ADB запущенный эмулятор. Если нет, то ждем еще

ADB видит подключенный к нему эмулятор:

Запускаем тестран:

./gradlew connectedAndroidTest --no-daemon

Ура! Тест пройден!

Негативный тест

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

Подготовка:

  • Переустановка VirtualBox на родительской машине (чтобы избежать ошибочное сохранение конфигов)

sudo apt purge virtualbox-6.1
  • VM мы создаём без проброса виртуализации и с одним CPU:

  • В созданной VM мы не устанавливаем:

    • VBoxClient

    • KVM

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

Ура! Тест не пройден! Никогда еще так не радовался проваленному тестрану:

Падает PackageManager, как и обычно при запуске из виртуальной среды без аппаратной поддержки процессора:

Заключение

Мы сделали первый этап построения инфраструктуры для проведения автотестов Android приложений. Следующим этапом должно стать упаковка описанного выше сценария в Packer (ссылка на официальный сайт) который умеет работать с образами VirtualBox. Затем весь сценарий мы попробуем запустить из CI Jenkins. Если учесть, что плагин для него уже порядком устарел, то будет очень интересно.

Все результаты опубликую, как пополнения к этой статье.

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

Спасибо большое за внимание!

П.С.

Можно Вас в комментариях попросить привести пример Вашей инфраструктуры с использованием Android эмулятора? К примеру, эмуляторы в докер-контейнерах (https://github.com/budtmo/docker-android) может быть еще какие-нибудь интересные примеры.

Подробнее..

Инверсия контроля на голом TypeScript без боли

10.02.2021 18:20:17 | Автор: admin

Здравствуйте, меня зовут Дмитрий Карловский и (сколько себя помню) я борюсь со своим окружением. Ведь оно такое костное, дубовое, и никогда не понимает, что я от него хочу. Но в какой-то момент я понял, что хватит это терпеть и надо что-то менять. Поэтому теперь не окружение диктует мне, что я могу и не могу делать, а я диктую окружению каким ему быть.

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

Итак, что мы хотим получить:

  • Функции при вызове наследуют контекст у вызвавшей их функции

  • Объекты наследуют контекст у их объекта-владельца

  • В системе может существовать одновременно множество вариантов контекста

  • Изменения в производных контекстах не влияют на исходный

  • Изменения в исходном контексте отражаются на производных

  • Тесты могут запускаться в изолированном и не изолированном контексте

  • Минимум бойлерплейта

  • Максимум перфоманса

  • Тайпчек всего этого

Давайте, объявим какую-нибудь глобальную константу в глобальном контексте окружения:

namespace $ {    export let $user_name: string = 'Anonymous'}

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

namespace $ {    export function $log( this: $, ... params: unknown[] ) {        console.log( ... params )    }}

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

$log( 123 ) // Error

Вызвать её можно исключительно из какого-либо контекста окружения. Например, из глобального контекста:

$.$log( 123 ) // OK

Однако, пока что $ у нас - это неймспейс, а не тип. Давайте для простоты создадим и одноимённый тип:

namespace $ {    export type $ = typeof $}

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

namespace $ {    export function $hello( this: $ ) {        this.$log( 'Hello ' + this.$user_name )    }}

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

namespace $ {    export function $ambient(        this: $,        over: Partial&lt; $ >,    ): $ {        const context = Object.create( this )        for( const field of Object.getOwnPropertyNames( over ) ) {            const descr = Object.getOwnPropertyDescriptor( over, field )!            Object.defineProperty( context, field, descr )        }        return context    }}

Object.create мы используем, чтобы создание производного контекста было быстрым, даже если он разрастётся. А вот Object.assign не используется, чтобы в переопределениях можно было задавать не только значения, но и геттеры, и сеттеры. Эта фабрика нам ещё пригодится, а пока давайте напишем наш первый тест:

namespace $.test {    export function $hello_greets_anon_by_default( this: $ ) {        const logs = [] as unknown[]        this.$log = logs.push.bind( logs )        this.$hello()        this.$assert( logs, [ 'Hello Anonymous' ] )    }}

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

namespace $ {    export function $assert&lt; Value >( a: Value, b: Value ) {        const sa = JSON.stringify( a, null, '\t' )        const sb = JSON.stringify( b, null, '\t' )        if( sa === sb ) return        throw new Error( `Not equal\n${sa}\n${sb}`)    }}

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

namespace $ {    export async function $test_run( this: $ ) {        for( const test of Object.values( this.$test ) ) {            await test.call( this.$isolated() )        }        this.$log( 'All tests passed' )    }}

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

namespace $ {    export function $isolated( this: $ ) {        return this.$ambient({})    }}

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

namespace $ {    const base = $isolated    $.$isolated = function( this: $ ) {        return base.call( this ).$ambient({            $log: ()=> {}        })    }}

Теперь мы уверены, что любые тесты по умолчанию не будут ничего писать в реальную консоль даже если мы не переопределим в них функцию $log.

Давайте так же напишем и тест, что наши переопределения контекстов работают исправно:

namespace $.test {    export function $hello_greets_overrided_name( this: $ ) {        const logs = [] as unknown[]        this.$log = logs.push.bind( logs )        const context = this.$ambient({ $user_name: 'Jin' })        context.$hello()        this.$hello()        this.$assert( logs, [ 'Hello Jin', 'Hello Anonymous' ] )    }}

Теперь перейдём к объектам. Для простоты работы с контекстами введём простой базовый класс для всех наших классов:

namespace $ {    export class $thing {        constructor( private _$: $ ) {}        get $() { return this._$ }    }}

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

namespace $ {    export class $hello_card extends $thing {        get $() {            return super.$.$ambient({                $user_name: super.$.$user_name + '!'            })        }        get user_name() {            return this.$.$user_name        }        set user_name( next: string ) {            this.$.$user_name = next        }        run() {            this.$.$hello()        }    }}

Напишем тест, чтобы удостовериться, что это действительно работает:

namespace $.test {    export function $hello_card_greets_anon_with_suffix( this: $ ) {        const logs = [] as unknown[]        this.$log = logs.push.bind( logs )        const card = new $hello_card( this )        card.run()        this.$assert( logs, [ 'Hello Anonymous!' ] )    }}

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

namespace $ {    export class $hello_page extends $thing {        get $() {            return super.$.$ambient({                $user_name: 'Jin'            })        }        @ $mem        get Card() {            return new this.$.$hello_card( this.$ )        }        get user_name() {            return this.Card.user_name        }        set user_name( next: string ) {            this.Card.user_name = next        }        run() {            this.Card.run()        }    }}

Выносим создание владеимого объекта в отдельное свойство. Инъектим в него текущий контекст. И мемоизируем результат с помощью $mem. Возьмём самую простую его реализацию без реактивности:

namespace $ {    export function $mem(        host: object,        field: string,        descr: PropertyDescriptor,    ) {        const store = new WeakMap&lt; object, any >()        return {            ... descr,            get() {                let val = store.get( this )                if( val !== undefined ) return val                val = descr.get!.call( this )                store.set( this, val )                return val            }        }    }}

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

namespace $.test {    export function $hello_page_greets_overrided_name_with_suffix( this: $ ) {        const logs = [] as unknown[]        this.$log = logs.push.bind( logs )        const page = new $hello_page( this )        page.run()        this.$assert( logs, [ 'Hello Jin!' ] )    }}

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

namespace $ {    export class $app_card extends $.$hello_card {        get $() {            const form = this            return super.$.$ambient({                get $user_name() { return form.user_name },                set $user_name( next: string ) { form.user_name = next }            })        }        get user_name() {            return super.$.$storage_local.getItem( 'user_name' ) ?? super.$.$user_name        }        set user_name( next: string ) {            super.$.$storage_local.setItem( 'user_name', next )        }    }}

Само локальное хранилище - это просто алиас для нативного объекта:

namespace $ {    export const $storage_local: Storage = window.localStorage}

А раз оно у нас персистится, то нужно нужно рядом положить и мок, который сохраняет данные не в нативное хранилище, а во временный объект:

namespace $ {    const base = $isolated    $.$isolated = function( this: $ ) {        const state = new Map&lt; string, string >()        return base.call( this ).$ambient({            $storage_local: {                getItem( key: string ){ return state.get( key ) ?? null },                setItem( key: string, val: string ) { state.set( key, val ) },                removeItem( key: string ) { state.delete( key ) },                key( index: number ) { return [ ... state.keys() ][ index ] ?? null },                get length() { return state.size },                clear() { state.clear() },            }        })    }}

Теперь мы, наконец, можем реализовать наше приложение, которое подменяет в контексте исходный класс $hello_card на свой $app_card, и всё поддерево объектов будет инстанцировать именно его.

namespace $ {    export class $app extends $thing {        get $() {            return super.$.$ambient({                $hello_card: $app_card,            })        }        @ $mem        get Hello() {            return new this.$.$hello_page( this.$ )        }        get user_name() {            return this.Hello.user_name        }        rename() {            this.Hello.user_name = 'John'        }    }}

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

namespace $.$test {    export function $changable_user_name_in_object_tree( this: $ ) {        const name_old = this.$storage_local.getItem( 'user_name' )        this.$storage_local.removeItem( 'user_name' )        const app1 = new $app( this )        this.$assert( app1.user_name, 'Jin!' )        app1.rename()        this.$assert( app1.user_name, 'John' )        const app2 = new $app( this )        this.$assert( app2.user_name, 'John' )        this.$storage_local.removeItem( 'user_name' )        this.$assert( app2.user_name, 'Jin!' )        if( name_old !== null ) {            this.$storage_local.setItem( 'user_name', name_old )        }    }}

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

Запустим тесты в полностью изолированном контексте, чтобы проверить, что реализовали всю нашу логику правильно:

namespace $ {    await $.$test_run()}

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

    $.$ambient({        $isolated: function(){ return $.$ambient({}) }    }).$test_run()}

Этот второй вариант, если запустить в Сафари в порно режиме, выдаст исключение, так как в нём нельзя обращаться к localStorage, а этот кейс в нашей нативной реализации $storage_local не предусмотрен.

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

Подробнее об этом подходе к тестированию можно ознакомиться в моём выступлении на TechLeadConf: Фрактальное Тестирование.

Разобранный же тут подход к инверсии контроля активно применяется во фреймворке $mol, что даёт ему потрясающую гибкость и простоту кода. Но это уже совсем другая история

Если вас смущает общий неймспейс и отcутствие import/export, то можете ознакомиться с этим анализом: Fully Qualified Names vs Imports. А если смущает именование через подчёркивание, то с этим: PascalCase vs camelCase vs kebab case vs snake_case.

TypeScript песочница со всем кодом из статьи.

Подробнее..

Тестирование From Zero to Hero. Часть 1

28.01.2021 14:21:54 | Автор: admin

Всем привет! Меня зовут Сергей, я работаю в команде Тинькофф. Сегодня я хочу рассказать, как мы в Тинькофф приходили к классической пирамиде тестирования.

Рассказ будет в трех частях:

  • Трудности, с которыми нам пришлось столкнуться, и как мы их преодолевали.

  • Конкретные решения по некоторым распространенным кейсам при написании интеграционных тестов.

  • Подход к написанию E2E-тестов (тестов, покрывающих взаимодействие всех систем приложения, включая back-end и пользовательский интерфейс) с использованием паттерна PageObject, пришедшего к нам из мира веб-разработки.

Вместо предисловия

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

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

Этап принятия

Ранее E2E-тесты писали только автоматизаторы, которых было несколько человек. Тестами покрывалась только малая часть основного функционала. Регресс-тестирование начало затягиваться, и выкатывать фичи в прод стало труднее с учетом роста команды.

Первый шаг, который мы сделали, передали написание E2E-тестов разработчикам, причем на обязательной основе. Ни одна фича не может перейти в статус Ready если она не покрыта E2E-тестами. Поскольку критерии приемки задачи описывают QA-специалисты, то они и писали тест-кейсы для каждой фичи.

Тест-кейсы

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

Пример тест кейсаПример тест кейса

Тут мы столкнулись с несколькими проблемами:

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

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

    Мы имели три кейса, каждый из которых проверял отдельно каждый экран и не проверял пользовательский флоу.

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

По итогу мы получали пирамиду тестирования в виде песочных часов. Мы делали много Unit- и E2E-тестов и практически не писали интеграционных тестов.

Почему это не круто?

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

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

  • Тесты запускаются на эмуляторах. Сам тест проходит долго, потому как он имитирует реальные действия пользователя (одна только авторизация длится около 30 секунд на каждом запуске теста).

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

  • Из-за того, что прогоны тестов проводятся на CI, иногда достаточно трудно понять, по какой именно причине сломался тест.

  • Е2Е-тесты имеют свойство падать без каких-либо изменений в коде (нестабильность интернета, нестабильность тестового фреймворка, проблемы на реальном устройстве и другие причины), именно поэтому их еще называют flaky tests.

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

Почему так?

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

Для E2E-тестов понятно, что на тестовом окружении должно быть поднято все, а для интеграционного только часть и не всегда понятно какая. Это приводило к тому, что всем было проще не разбираться и писать E2E тесты. Это негативно сказывалось на времени прогона тестов в целом.

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

Что мы сделали

Командный тренинг

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

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

Пользовательские истории

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

Небольшой пример

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

  • Экран списка справок.

  • Экран настроек справки (язык, период и так далее).

  • Экран превью справки.

Как бы это выглядело раньше?

Мы бы проверили Экран списка справок: UI-элементы, отправляемые запросы. После чего аналогичные проверки были бы на другие два экрана.

Здесь было две проблемы:

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

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

Как это сделано сейчас?

Тест-кейс != Е2Е-тест.

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

Сам тест разделяется на шаги (steps). Обычно шаг это один экран внутри всего флоу (но бывают и другие ситуации). Мы пишем в description название этого шага, делаем скриншот и все проверки, которые касаются этого шага.

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

И есть кейсы, в которых проверяется какое-то конкретное действие, например отправка запроса при нажатии на кнопку. Для таких тестов достаточно написать Unit-тест либо интеграционный тест. Такие ошибки мы можем отлавливать на стадии пул-реквестов и исправлять их до того, как они попадут к QA.

Пирамида

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

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

Мы с ребятами начали разбивать монолитные компоненты на фича-модули. Наша задача вынести компоненты приложения, которыми пользовались все фича-команды (сетевые слои, работа с базами данных, аналитика и другие).

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

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

  • Выделили отдельные Аndroid-модули для тестовых компонентов.

  • Подготовили базовые моки (сущности хранения сессий, базы данных и так далее).

  • Написали документацию по основным принципам тестирования с примерами.

Итоги

Мы пришли к осмысленной, правильной пирамиде тестирования.

  • Начали более сбалансированно распределять тесты по слоям.

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

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

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

Подробнее..

Рефакторинг пет проекта докеризация, метрики, тесты

17.02.2021 20:19:33 | Автор: admin

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

Предыстория

Пару лет назад я решил тряхнуть стариной и поиграть в LineAge II на одном из популярных пиратских серверов. В этой игре есть один игровой процесс, в котором требуется "поговорить" с ящиками после смерти 4 боссов. Ящик стоит после смерти 2 минуты. Сами боссы после смерти появляются спустя 24 +/- 6ч, то есть шанс появится есть как через 18ч, так и через 30ч. У меня на тот момент была фуллтайм работа, да и в целом не было времени ждать эти ящики. Но нескольким моим персонажам требовалось пройти этот квест, поэтому я решил "автоматизировать" этот процесс. На сайте сервера есть RSS фид в формет XML, где публикуются события с серверов, включая события смерти босса.

Задумка была следующей:

  • получить данные с RSS

  • сравнить данные с локальной копией в базе данных

  • если есть разница данных - сообщить об этом в телеграм канал

  • отдельно сообщать если босса не убили за первые 9ч сообщением "осталось 3ч", и "осталось 1,5ч". Допустим вечером пришло сообщение, что осталось 3ч, значит смерть босса будет до того, как я пойду спать.

Код на php был написан быстро и в итоге у меня было 3 php файла. Один был с god object классом, а другие два запускали программу в двух режимах - парсер новых, или проверка есть ли боссы на максимальном "респе". Запускал я их крон командами. Это работало и решало мою проблему.

Другие игроки замечали, что я появляюсь в игре сразу после смерти боссов, и через 10 дней у меня на канале было около 50 подписчиков. Так же попросили сделать такое же для второго сервера этого пиратского сервиса. Задачу я тоже решил копипастой. В итоге у меня уже 4 файла с почти одинаковым кодом, и файл с god object. Потом меня попросили сделать то же самое для третьего сервера этого пиратского сервиса. И это отлично работало полтора года.

В итоге у меня спустя полтора года:

  • у меня 6 файлов, дублируют себя почти полностью (по 2 файла на сервер)

  • один god object на несколько сотен строк

  • MySQL и Redis на сервере, где разместил код

  • cron задачи, которые запускают файлы

  • ~1400 подписчиков на канале в телеграм

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

Ожидаемый результат после рефакторинга

  1. Отрефакторить код так, чтобы легче было вносить изменения. Важный момент - отрефакторить без изменения бизнес логики, по сути раскидать god object по файлам, сам код не править, иначе это затянет сроки. Следовать PSR-12.

  2. Докеризировать воркера для удобства переноса на другой сервер и прозрачность запуска и остановки

  3. Запускать воркера через supervisor

  4. Внедрить процесс тестирования кода, настроить Codeception

  5. Докеризировать MySQL и Redis

  6. Настроить Github Actions для запуска тестов и проверки на code style

  7. Поднять Prometheus, Grafana для метрик и мониторинга работоспособности

  8. Сделать докер контейнер, который будет отдавать метрики на страницу /metrics для Prometheus

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

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

Шаг 1. Рефакторинг приложения

Одним из требований было не потратить на это недели, поэтому основные классы я решил сделать наследниками Singleton

<?phpdeclare(strict_types=1);namespace AsteriosBot\Core\Support;use AsteriosBot\Core\Exception\DeserializeException;use AsteriosBot\Core\Exception\SerializeException;class Singleton{    protected static $instances = [];    /**     * Singleton constructor.     */    protected function __construct()    {        // do nothing    }    /**     * Disable clone object.     */    protected function __clone()    {        // do nothing    }    /**     * Disable serialize object.     *     * @throws SerializeException     */    public function __sleep()    {        throw new SerializeException("Cannot serialize singleton");    }    /**     * Disable deserialize object.     *     * @throws DeserializeException     */    public function __wakeup()    {        throw new DeserializeException("Cannot deserialize singleton");    }    /**     * @return static     */    public static function getInstance(): Singleton    {        $subclass = static::class;        if (!isset(self::$instances[$subclass])) {            self::$instances[$subclass] = new static();        }        return self::$instances[$subclass];    }}

Таким образом вызов любого класса, который от него наследуются, можно делать методом getInstance()

Вот так, например, выглядел класс подключения к базе данных

<?phpdeclare(strict_types=1);namespace AsteriosBot\Core\Connection;use AsteriosBot\Core\App;use AsteriosBot\Core\Support\Config;use AsteriosBot\Core\Support\Singleton;use FaaPz\PDO\Database as DB;class Database extends Singleton{    /**     * @var DB     */    protected DB $connection;    /**     * @var Config     */    protected Config $config;    /**     * Database constructor.     */    protected function __construct()    {        $this->config = App::getInstance()->getConfig();        $dto = $this->config->getDatabaseDTO();        $this->connection = new DB($dto->getDsn(), $dto->getUser(), $dto->getPassword());    }    /**     * @return DB     */    public function getConnection(): DB    {        return $this->connection;    }}

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

Шаг 2: Докеризация воркеров

Запуск всех контейнеров я сделал через docker-compose.yml

Конфиг сервиса для воркеров выглядит так:

  worker:    build:      context: .      dockerfile: docker/worker/Dockerfile    container_name: 'asterios-bot-worker'    restart: always    volumes:      - .:/app/    networks:      - tier

А сам docker/worker/Dockerfile выглядит так:

FROM php:7.4.3-alpine3.11# Copy the application codeCOPY . /appRUN apk update && apk add --no-cache \    build-base shadow vim curl supervisor \    php7 \    php7-fpm \    php7-common \    php7-pdo \    php7-pdo_mysql \    php7-mysqli \    php7-mcrypt \    php7-mbstring \    php7-xml \    php7-simplexml \    php7-openssl \    php7-json \    php7-phar \    php7-zip \    php7-gd \    php7-dom \    php7-session \    php7-zlib \    php7-redis \    php7-session# Add and Enable PHP-PDO ExtenstionsRUN docker-php-ext-install pdo pdo_mysqlRUN docker-php-ext-enable pdo_mysql# RedisRUN apk add --no-cache pcre-dev $PHPIZE_DEPS \        && pecl install redis \        && docker-php-ext-enable redis.so# Install PHP ComposerRUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer# Remove CacheRUN rm -rf /var/cache/apk/*# setup supervisorADD docker/supervisor/asterios.conf /etc/supervisor/conf.d/asterios.confADD docker/supervisor/supervisord.conf /etc/supervisord.confVOLUME ["/app"]WORKDIR /appRUN composer installCMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]

Обратите внимание на последнюю строку в Dockerfile, там я запускаю supervisord, который будет мониторить работу воркеров.

Шаг 3: Настройка supervisor

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

Код файла worker.php

<?phprequire __DIR__ . '/vendor/autoload.php';use AsteriosBot\Channel\Checker;use AsteriosBot\Channel\Parser;use AsteriosBot\Core\App;use AsteriosBot\Core\Connection\Log;$app = App::getInstance();$checker = new Checker();$parser = new Parser();$servers = $app->getConfig()->getEnableServers();$logger = Log::getInstance()->getLogger();$expectedTime = time() + 60; // +1 min in seconds$oneSecond = time();while (true) {    $now = time();    if ($now >= $oneSecond) {        $oneSecond = $now + 1;        try {            foreach ($servers as $server) {                $parser->execute($server);                $checker->execute($server);            }        } catch (\Throwable $e) {            $logger->error($e->getMessage(), $e->getTrace());        }    }    if ($expectedTime < $now) {        die(0);    }}

У RSS есть защита от спама, поэтому пришлось сделать проверку на секунды и посылать не более 1го запроса в секунду. Таким образом мой воркер каждую секунду выполняет 2 действия, сначала проверяет rss, а затем калькулирует время боссов для сообщений о старте или окончании времени респауна боссов. После 1 минуты работы воркер умирает, и его перезапускает supervisor

Сам конфиг supervisor выглядит так:

[program:worker]command = php /app/worker.phpstderr_logfile=/app/logs/supervisor/worker.lognumprocs = 1user = rootstartsecs = 3startretries = 10exitcodes = 0,2stopsignal = SIGINTreloadsignal = SIGHUPstopwaitsecs = 10autostart = trueautorestart = truestdout_logfile = /dev/stdoutstdout_logfile_maxbytes = 0redirect_stderr = true

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

[supervisord]nodaemon=true[include]files = /etc/supervisor/conf.d/*.conf

Набор полезных команд supervisorctl:

supervisorctl status       # статус воркеровsupervisorctl stop all     # остановить все воркераsupervisorctl start all    # запустить все воркераsupervisorctl start worker # запустить один воркера с конфига, блок [program:worker]

Шаг 4: Настройка Codeception

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

# Codeception Test Suite Configuration## Suite for unit or integration tests.actor: UnitTestermodules:    enabled:        - Asserts        - \Helper\Unit        - Db:              dsn: 'mysql:host=mysql;port=3306;dbname=test_db;'              user: 'root'              password: 'password'              dump: 'tests/_data/dump.sql'              populate: true              cleanup: true              reconnect: true              waitlock: 10              initial_queries:                - 'CREATE DATABASE IF NOT EXISTS test_db;'                - 'USE test_db;'                - 'SET NAMES utf8;'    step_decorators: ~

Шаг 5: Докеризация MySQL и Redis

На сервере, где работало это приложение, у меня было еще пара других ботов. Все они использовали один сервер MySQL и один Redis для кеша. Я решил вынести все, что связано с окружением в отельный docker-compose.yml, а самих ботов залинковать через внешний docker network

Выглядит это так:

version: '3'services:  mysql:    image: mysql:5.7.22    container_name: 'telegram-bots-mysql'    restart: always    ports:      - "3306:3306"    environment:      MYSQL_ROOT_PASSWORD: "${DB_PASSWORD}"      MYSQL_ROOT_HOST: '%'    volumes:      - ./docker/sql/dump.sql:/docker-entrypoint-initdb.d/dump.sql    networks:      - tier  redis:    container_name: 'telegram-bots-redis'    image: redis:3.2    restart: always    ports:      - "127.0.0.1:6379:6379/tcp"    networks:      - tier  pma:    image: phpmyadmin/phpmyadmin    container_name: 'telegram-bots-pma'    environment:      PMA_HOST: mysql      PMA_PORT: 3306      MYSQL_ROOT_PASSWORD: "${DB_PASSWORD}"    ports:      - '8006:80'    networks:      - tiernetworks:  tier:    external:      name: telegram-bots-network

DB_PASSWORD я храню в .env файле, а ./docker/sql/dump.sql у меня лежит бекап для инициализации базы данных. Так же я добавил external network так же, как в этом конфиге - в каждом docker-compose.yml каждого бота на сервере. Таким образом они все находятся в одной сети и могут использовать общие базу данных и редис.

Шаг 6: Настройка Github Actions

В шаге 4 этого туториала я добавил тестовый фреймфорк Codeception, который для тестирования требует базу данных. В самом проекте нет базы, в шаге 5 я ее вынес отдельно и залинковал через external docker network. Для запуска тестов в Github Actions я решил полностью собрать все необходимое на лету так же через docker-compose.

name: Actionson:  pull_request:    branches: [master]  push:    branches: [master]jobs:  build:    runs-on: ubuntu-latest    steps:      - name: Checkout        uses: actions/checkout@v2      - name: Get Composer Cache Directory        id: composer-cache        run: |          echo "::set-output name=dir::$(composer config cache-files-dir)"      - uses: actions/cache@v1        with:          path: ${{ steps.composer-cache.outputs.dir }}          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}          restore-keys: |            ${{ runner.os }}-composer-      - name: Composer validate        run: composer validate      - name: Composer Install        run: composer install --dev --no-interaction --no-ansi --prefer-dist --no-suggest --ignore-platform-reqs      - name: PHPCS check        run: php vendor/bin/phpcs --standard=psr12 app/ -n      - name: Create env file        run: |          cp .env.github.actions .env      - name: Build the docker-compose stack        run: docker-compose -f docker-compose.github.actions.yml -p asterios-tests up -d      - name: Sleep        uses: jakejarvis/wait-action@master        with:          time: '30s'      - name: Run test suite        run: docker-compose -f docker-compose.github.actions.yml -p asterios-tests exec -T php vendor/bin/codecept run unit

Инструкция onуправляет когда билд триггернётся. В моем случае - при создании пулл реквеста или при коммите в мастер.

Инструкция uses: actions/checkout@v2 запускает проверку доступа процесса к репозиторию.

Далее идет проверка кеша композера, и установка пакетов, если в кеше не найдено

Затем в строке run: php vendor/bin/phpcs --standard=psr12 app/ -nя запускаю проверку кода соответствию стандарту PSR-12 в папке ./app

Так как тут у меня специфическое окружение, я подготовил файл .env.github.actionsкоторый копируется в .env Cодержимое .env.github.actions

SERVICE_ROLE=testTG_API=XXXXXTG_ADMIN_ID=123TG_NAME=AsteriosRBbotDB_HOST=mysqlDB_NAME=rootDB_PORT=3306DB_CHARSET=utf8DB_USERNAME=rootDB_PASSWORD=passwordLOG_PATH=./logs/DB_NAME_TEST=test_dbREDIS_HOST=redisREDIS_PORT=6379REDIS_DB=0SILENT_MODE=trueFILLER_MODE=true

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

Затем я собираю проект при помощи docker-compose.github.actions.ymlв котором прописано все необходимое для тестирвания, контейнер с проектом и база данных. Содержимое docker-compose.github.actions.yml:

version: '3'services:  php:    build:      context: .      dockerfile: docker/php/Dockerfile    container_name: 'asterios-tests-php'    volumes:      - .:/app/    networks:      - asterios-tests-network  mysql:    image: mysql:5.7.22    container_name: 'asterios-tests-mysql'    restart: always    ports:      - "3306:3306"    environment:      MYSQL_DATABASE: asterios      MYSQL_ROOT_PASSWORD: password    volumes:      - ./tests/_data/dump.sql:/docker-entrypoint-initdb.d/dump.sql    networks:      - asterios-tests-network##  redis:#    container_name: 'asterios-tests-redis'#    image: redis:3.2#    ports:#      - "127.0.0.1:6379:6379/tcp"#    networks:#      - asterios-tests-networknetworks:  asterios-tests-network:    driver: bridge

Я закомментировал контейнер с Redis, но оставил возможность использовать его в будущем. Сборка с кастомным docker-compose файлом, а затем тесты - запускается так

docker-compose -f docker-compose.github.actions.yml -p asterios-tests up -ddocker-compose -f docker-compose.github.actions.yml -p asterios-tests exec -T php vendor/bin/codecept run unit

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

Шаг 7: Настройка Prometheus и Grafana

В шаге 5 я вынес MySQL и Redis в отдельный docker-compose.yml. Так как Prometheus и Grafana тоже общие для всех моих телеграм ботов, я их добавил туда же. Сам конфиг этих контейнеров выглядит так:

  prometheus:    image: prom/prometheus:v2.0.0    command:      - '--config.file=/etc/prometheus/prometheus.yml'    restart: always    ports:      - 9090:9090    volumes:      - ./prometheus.yml:/etc/prometheus/prometheus.yml    networks:      - tier  grafana:    container_name: 'telegram-bots-grafana'    image: grafana/grafana:7.1.1    ports:      - 3000:3000    environment:      - GF_RENDERING_SERVER_URL=http://renderer:8081/render      - GF_RENDERING_CALLBACK_URL=http://grafana:3000/      - GF_LOG_FILTERS=rendering:debug    volumes:      - ./grafana.ini:/etc/grafana/grafana.ini      - grafanadata:/var/lib/grafana    networks:      - tier    restart: always  renderer:    image: grafana/grafana-image-renderer:latest    container_name: 'telegram-bots-grafana-renderer'    restart: always    ports:      - 8081    networks:      - tier

Они так же залинкованы одной сетью, которая потом линкуется с external docker network.

Prometheus: я прокидываю свой конфиг prometheus.yml, где я могу указать источники для парсинга метрик

Grafana: я создаю volume, где будут храниться конфиги и установленные плагины. Так же я прокидываю ссылку на сервис рендеринга графиков, который мне понадобиться для отправки alert. С этим плагином alert приходит со скриншотом графика.

Поднимаю проект и устанавливаю плагин, затем перезапускаю Grafana контейнер

docker-compose up -ddocker-compose exec grafana grafana-cli plugins install grafana-image-rendererdocker-compose stop  grafana docker-compose up -d grafana

Шаг 8: Публикация метрик приложения

Для сбора и публикации метрик я использовал endclothing/prometheus_client_php

Так выглядит мой класс для метрик

<?phpdeclare(strict_types=1);namespace AsteriosBot\Core\Connection;use AsteriosBot\Core\App;use AsteriosBot\Core\Support\Singleton;use Prometheus\CollectorRegistry;use Prometheus\Exception\MetricsRegistrationException;use Prometheus\Storage\Redis;class Metrics extends Singleton{    private const METRIC_HEALTH_CHECK_PREFIX = 'healthcheck_';    /**     * @var CollectorRegistry     */    private $registry;    protected function __construct()    {        $dto = App::getInstance()->getConfig()->getRedisDTO();        Redis::setDefaultOptions(            [                'host' => $dto->getHost(),                'port' => $dto->getPort(),                'database' => $dto->getDatabase(),                'password' => null,                'timeout' => 0.1, // in seconds                'read_timeout' => '10', // in seconds                'persistent_connections' => false            ]        );        $this->registry = CollectorRegistry::getDefault();    }    /**     * @return CollectorRegistry     */    public function getRegistry(): CollectorRegistry    {        return $this->registry;    }    /**     * @param string $metricName     *     * @throws MetricsRegistrationException     */    public function increaseMetric(string $metricName): void    {        $counter = $this->registry->getOrRegisterCounter('asterios_bot', $metricName, 'it increases');        $counter->incBy(1, []);    }    /**     * @param string $serverName     *     * @throws MetricsRegistrationException     */    public function increaseHealthCheck(string $serverName): void    {        $prefix = App::getInstance()->getConfig()->isTestServer() ? 'test_' : '';        $this->increaseMetric($prefix . self::METRIC_HEALTH_CHECK_PREFIX . $serverName);    }}

Для проверки работоспособности парсера мне нужно сохранить метрику в Redis после получения данных с RSS. Если данные получены, значит все нормально, и можно сохранить метрику

        if ($counter) {            $this->metrics->increaseHealthCheck($serverName);        }

Где переменная $counter это количество записей в RSS. Там будет 0, если получить данные не удалось, и значит метрика не будет сохранена. Это потом понадобится для alert по работе сервиса.

Затем нужно метрики опубликовать на странице /metric чтобы Prometheus их спарсил. Добавим хост в конфиг prometheus.yml из шага 7.

# my global configglobal:  scrape_interval:     5s # Set the scrape interval to every 15 seconds. Default is every 1 minute.  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.  # scrape_timeout is set to the global default (10s).scrape_configs:  - job_name: 'bots-env'    static_configs:      - targets:          - prometheus:9090          - pushgateway:9091          - grafana:3000          - metrics:80 # тут будут мои метрики по uri /metrics

Код, который вытащит метрики из Redis и создаст страницу в текстовом формате. Эту страничку будет парсить Prometheus

$metrics = Metrics::getInstance();$renderer = new RenderTextFormat();$result = $renderer->render($metrics->getRegistry()->getMetricFamilySamples());header('Content-type: ' . RenderTextFormat::MIME_TYPE);echo $result;

Теперь настроим сам дашборд и alert. В настройках Grafana сначала укажите свой Prometheus как основной источник данных, а так же я добавил основной канал нотификации Телеграм (там добавляете токен своего бота и свой chat_id с этим ботом)

Настройка GrafanaНастройка Grafana
  1. Метрика increase(asterios_bot_healthcheck_x3[1m]) Показывает на сколько метрика asterios_bot_healthcheck_x3 увеличилась за 1 минуту

  2. Название метрики (будет под графиком)

  3. Название для легенды в пункте 4.

  4. Легенда справа из пункта 3.

  1. Правило, по которому проверяется метрика. В моем случае проверяет что за последние 30 секунд проблем не было

  2. Правило, по которому будет срабатывать alert. В моем случае "Когда сумма из метрики А между сейчас и 10 секунд назад"

  3. Если нет данных вообще - слать alert

  4. Сообщение в alert

Выглядит alert в телеграм так (помните мы настраивали рендеринг картинок для alert?)

Alert в ТелеграмAlert в Телеграм
  1. Обратите внимание, alert заметил падение, но все восстановилось. Grafana приготовилась слать alert, но передумала. Это то самое правило 30 секунд

  2. Тут уже все упало больше чем на 30 секунд и alert был отправлен

  3. Сообщение, которое мы указали в настройках alert

  4. Ссылка на dashboard

  5. Источник метрики

Шаг 9: Телеграм бот

Настройка телеграм бота ничем не отличается от настройки воркера. Телеграм бот по сути у меня это еще один воркер, я запустил его при помощи добавления настроек в supervisor. Тут уже рефакторинг проекта дал свои плоды, запуск бота был быстрым и простым.

Итоги

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

Ссылки на проекты

Подробнее..

Как я решил протестировать нагрузочную способность web сервера

20.04.2021 10:21:38 | Автор: admin

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

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

И так дано - web сервер. Написан на .net core. Сервер используется в корпоративной разработке.

Посмотреть, как работает можно, например здесь бесплатный сервис хранение ссылок http://linkin.link. Про него я писал тут http://personeltest.ru/aways/habr.com/ru/users/developer7/posts

Вступление

Собственно, как тестировать web сервер? Если посмотреть на проблему в лоб то веб сервер должен отдавать все страницы, которые были запрошены клиентами. И желательно отдавать быстро.

Конечно, есть множество других сценариев. Ведь web сервер не работает сам по себе, как правило, он как минимум работает с какой-нибудь БД, связь с которой это отдельная большая тема. Опять же могут быть множество отдельных сервисов автаризационных и прочих. Перед web сервером может стоять балансировщик нагрузок. Всё это может размещается в множестве виртуальных контейнеров, которые опять же могут быть на разных физических серверах. В общем это целый зоопарк каналов передачи данных каждый из которых может стать узким горлышком.

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

Задача поставлена. Тестировать решил так. На одной машине под windows 10 запускаю web сервер. На web сервере запущен сайт. На сайте размещено куча js, сss, mp4 файлов и, собственно, html страничка. Для простоты я просто взял страницу из готового сайта.

Чем досить сервер? Тут 2 пути скачать что-то готовое или написать свой велосипед. Я решил остановится на втором варианте. И этот выбор я сделал по нескольким причинам.

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

Httpdos

Сказано сделано.

Программа работает так - при старте читается файл urls.txt где занесены url которые надо скачивать. Далее нажимаем старт. Создаются список Task по количеству url. Каждый Task открывает socket, отправляет http запрос, получает данные, закрывает socket. Далее процедура повторяется.

Изначально использовал TcpClient но по каким-то причинам он выдавал почти сразу на старте множество ошибочных загрузок. Не стал разбираться, а спустился пониже. Реализовал отправку на socket.

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

Ошибки считал по 2 сценариям. Все exception связанные с открытием, отсылкой, приёмом данных. Собирательно если хоть, где что-то не так убиваем socket, инкрементируем счётчик ошибок. Ещё добавил проверку длины данных. При первой закачке страницы запоминается длина данных, все повторные закачки сверяются с этим числом. Отклонение считается ошибкой. Можно было бы добавить и что-то другое но для начала мне и этого достаточно.

Всё для эксперимента готово. Запускаем web сервер. Запускаем httpdos так назвал программу, чтобы дальше можно было использовать это короткое название. И вижу следующую картину.

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

Так же в последствии я добавил сбоку textarea куда вывожу все exception, и ещё решил добавить вывод - количество запросов в секунду.

Картинка меня порадовала. Во первых - всё работает ). Во вторых работает без ошибок. Количество обработанных запросов получилось где-то 4000-6000 в секунду.

Откуда такая цифра? По моим размышлениям она зависит от многих обстоятельств. Самая очевидное это какого размера сами запросы. Как я писал выше я просто скачиваю все данные с определённой web страницы, которая была взята из стороннего web проекта. И там много mp4 файлов, размер которых под 3 мегабайта. Если уменьшить размер запросов, например скачивать только css наверняка количество обработанных запросов увеличится. Мне даже стало интересно, и я начал играть с исходным кодом как со стороны web сервера, так и со стороны httpdos. Там есть куча различных таймеров, буферов и прочего. Я смотрел, как то или иное изменение, окажет влияние на скорость.

Но не буду писать об этом. Да и цель была проверить отказоустойчивость, а не скорость. Простая проверка для начала - зависнет ли сервер от dos атаки или просто будет медленнее обрабатывать запросы.

Так же существенное влияние на скорость оказывало, то что web сервер и httpdos был запущен в режиме отладки в Visual Studio.

Поработав пару минут ни одной ошибки. Посмотрел загрузку процессора диспетчер задач показал примерно 28% на web сервер и 20% на httpdos. Процессор стоит i7-8700k. Не разогнанный. Это 6 ядерный 12 поточный камень. В процессе работы куллер охлаждения не было слышно проц холодный. Специально температуру не смотрел.

Решил параллельно с httpdos сделать загрузки js файла через браузер. Файл закачивается мгновенно. Т.е. httpdos не оказывает существенного влияния на web сервер.

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

А у видел я, что по каким-то причинам спустя примерно минуту начали появляться ошибки загрузок.

И я начал расследование.

Такого поведения просто не должно быть!

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

То, что произошло дальше заставило меня напрячься.

В процессе экспериментов я и так и сяк изгалялся над httpdos. И один сценарий привёл к неожиданным результатам.

Если запустить не менее 3 программ. Думаю, что такое количество связанно с количеством одновременных запросов в секунду. Начать dos атаку. Дождаться появления ошибок. Потом резко закрыть программы. То слушающий socket web сервера просто умирает! Вот так нету никакой ошибки на стороне сервера. Все потоки работают. А socket ничего не принимает. Это уже ни в какие ворота.

Эксперименты показали, что socket оживал примерно через 4 минуты, но, если dos атаку проводить долго socket умирал навсегда, по крайней мере я минут 15 ждал оживления, а дальше уже и не интересно было. Такого поведения просто не должно быть!

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

Если перезапускать web сервер, т.е. получается мы пересоздаём слушающий socket, то socket начинал принимать клиентов сразу.

Первый шаг

Первое что я сделал - решил попробовать получить более подробную информацию из exсeption на стороне httpdos. Полазив по интернету, нашёл что мне нужен SocketException, а в нём посмотреть свойство ErrorCode. Сделано. Получил код ошибки 10061 - WSAECONNREFUSED. Тут пояснение.

В соединении отказано.

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

Ну информативно(сарказм) однако. Вроде как это стоит понимать, что socket, к которому мы хотим подключится как бы и нету.

Запускаем консоль. Вводим netstat -an и видим. Вот он родненький. Слушает 80 порт. Ну по крайней мере система так думает.

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

Пару слов о socket.

В веб сервере используется самый стандартный способ открытия и работы с socket в .net. Вот пример кода:

Socket listenSocket = new Socket(AddressFamily.InterNetwork,                                 SocketType.Stream, ProtocolType.Tcp){NoDelay = true,Blocking = false,ReceiveBufferSize = TLSpipe.TLSPlaintext_max_recive,SendBufferSize = TLSpipe.TLS_CHUNK};//~~~listenSocket.Bind(new IPEndPoint(point.ip, port.port));Socket socket = await listenSocket.AcceptAsync();

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

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

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

Soсket socket = await listenSocket.AcceptAsync();

Затем подвесил socket. Попытались присоединится клиентом. Программа висела в вышеприведённой строчки кода. Тут мы увидели, что проблема не в коде после socket, а где то внутри socket.

Дальше я решил поэкспериментировать с настройками открытия socket. Собственно, в своё время все настройки и так были исследованы, и оставлены только те, что нужно. Но в свете последних событий никто не уйдёт от (подозрений) экспериментов.

Под подозрения попали следующие настройки.

  1. ReceiveTimeout

  2. SendTimeout

  3. Ttl

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

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

Что касается тайм аутов. В коде сервера реализованы различные таймауты, но на более высоком уровне. В приёмнике есть таймауты на приём шапки http, определения длины body, расчёт времени на приём body исходя из длины body и сценария наихудшего канала связи, например 1024 кб/c.

Примерно такие же правила и на отправку.

И если таймауты выходят socket клиента закрывается и удаляется. Для socket вызывается shutdown и close. Всё как и предписывает microsoft.

Поставил таймауты для resive/transmite 2000ms. Не помогло. Идём далее.

Ttl

Что это за параметр? Wiki говорит:

Получает или задает значение, задающее время существования (TTL) IP-пакетов

Т.е. при прохождении очередного шлюза параметр уменьшается на 1. При достижении 0 пакет удаляется. И вроде это не наш случай. Потому как наша система вся на localhost. Но! При гугление я нашёл следующую информацию.

Там было сказано, что windows от этого параметра рассчитывает двойное время нахождения socket в режиме TIME_WAIT. Про этот режим более подробно ниже.

Поставил ttl в минимально возможное значение. Не помогло.

Утечка sockets?

Далее я подумал сервер открывает каждую секунду около 6000 тысяч sockets и закрывает их. И всё это крутится на кучи асинхронных Task. Вдруг количество открытых сокетов и закрытых не совпадает? И есть, некая утечка sockets?

Быстро набросав код принцип которого заключается в инкременте глобальной переменной при открытии socket и декременте при закрытии и вывода этого числа в консоль.

Весь дополнительный код состоял из 3 блоков:

Interlocked.Increment(ref Program.cnt);Interlocked.Decrement(ref Program.cnt);Task.Run(async () => { while (true) {await Task.Delay(1000);Console.WriteLine(Program.cnt); });}

Разумеется, каждый блок размещается в нужном месте.

Запустив программы, я получил следующее:

Видео можно разделить на три части. Запуск. Рабочее состояние длится около 3 минут. В этом состоянии ни одной ошибки. И первое появление ошибки. Собственно, вторую часть я вырезал как не интересную.

Как же нам интерпретировать результат? Во-первых, мы видим число примерно 400 выводящееся каждую секунду.

Как работает веб сервер? Идеальный сервер приняв клиента мгновенно формирует ответ, отправляет его клиенту и мгновенно закрывает socket (хотя в реальности socket не закрывается а ожидает следующих запросов, ну условимся что так работает идеальный сервер). Поэтому количество открытых socket в идеальном сервере каждую секунду было бы 0. Но тут мы видим, что каждую секунду у нас примерно 400 обрабатывающихся клиентов. Что ж, для неидеального сервера вполне норма. Вообще количество одновременных клиентов в нашем сервере задаётся глобальной настройкой. В данном случае 10000 что значительно выше 400.

Так же мы видим, что периодически подпрыгивающее значение до 1000-2000. Связанно это может быть с чем угодно. При желании можно и это выяснить. Может сборщик мусора, может что ещё. Но, собственно, ничего криминального в этом нет.

И вот появились первые ошибки. Я начал закрывать программы. На видео виден характерный момент при закрытии второй программы, в последней оставшейся программе, посыпались ошибки открытия socket. Это в очередной раз показал своё лицо баг который мы так пытаемся отловить, пока безуспешно. Одна радость повторяется он регулярно, поэтому хоть есть возможность поиска. Сама же эта ошибка нам пока ничего не даёт.

Главная цель текущего эксперимента сопоставить количество открытых и закрытых socket. Окончательная цифра 0. Всё, как и должно быть. Ладно идём дальше.

Динамический диапазон портов

Далее расследование завело меня в область настроек windows. Должны же быть какие-то настройки для работы tcp/ip стека? Гугление мне подкинуло множество, но особо я хотел бы остановится на наиболее подходящих к нашему случаю.

Собственно настройки tcp/ip стека для windows меня интересовали с самого начала. Я задавал себе вопросы - а какие вообще порты выделяются на клиенте? Да и количество портов как бы ограничено. Всего 65535 значений. Такое число обусловлено исторически переменной uint16 в протоколе TCP.

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

Я даже вначале проверил следующий кейс. Изначально в httpdos использовался стандартный для .net способ открытия socket клиента.

new TcpClient().Connect("127.0.0.1", 80);

Такой способ выдаёт socket с портом из динамического пула windows. Т.е. windows сама решает какой порт тебе выдать. Я поменял этот код на другой где я сам указывал порты. Но первый запуск показал, что диапазон выбранных мною портов был нашпигован как изюм открытыми портами, плюс доступ к некоторым портам выдавал странные ошибки о некорректном доступе. Это заставляло меня усложнять программу делать код поиска открытых портов

Поискав готовое решение я увидел, что это не простой способ в одну строчку. И я не стал развивать эту тему дальше. Windows виднее кокой мне порт дать.

Но какой же диапазон портов мне выдаёт Windows? Тут я узнал ответ. Честно ещё в десятке других мест. Правда в большинстве информация была устаревшая - мол диапазон 1025 до 5000. Но я из практики знал, что диапазон выделяется где-то от 50000.

В дальнейшем я и убедился в своей правоте. Как оказалось Microsoft изменила этот диапазон, и он составляет 49152-65535. И того где-то 15k портов. Явно меньше, чем 65535

Поэтому первая настройка увеличиваем этот диапазон.

Я применил следующую команду:

netsh int ipv4 set dynamicport tcp start=10000 num=55535

Команду запускаем под администратором. Проверить значение можно командой:

netsh int ipv4 show dynamicport tcp

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

Делаем вывод на стороне сервер, всех портов подключившихся клиентов. Ну и пытаемся увидеть какие-нибудь отклонения от нормы. Вдруг кусок выпадет из списка?

Добавляем код - 1 строка:

Console.WriteLine(((IPEndPoint)socket.RemoteEndPoint).Port);

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

Далее я начал закрывать программы и при закрытии второй программы серверный socket завис. Порты перестали выделятся. Во второй программе httpdos посыпались ошибки открытия socket.

Выводы проблема не в портах. По крайней мере порты явно не заканчиваются.

TIME WAIT

А что у нас есть вообще по протоколу TCP? Идём сюда и внимательно читаем.

Под подозрение попадает одно состояние TIME WAIT. Это одно из стояний, в котором может находится socket, т.е. пара ip+port. После его закрытия.

Получается, что мы закрыли socket но он ещё будет недоступен. Поиск показал, что это время находится в районе 4 минут.

Если бы мы играли в игру холодно-горячо, то я бы заорал горячо! Горячо! Похоже это именно наш случай.

Но зачем такое странное поведение. Опять же из справки получил пояснение. Т.к. протокол tcp/ip гуляет по сети пакетами, а сами пакеты могут пойти по разным маршрутам. И вообще застрять на некоторое время на каком-нибудь шлюзе, может получится ситуация, когда мы открыли socket, поработали с удалённым сервером. Закрыли socket. Потом этот socket открывает другая программа а ей начинают валится пакеты от предыдущей сессии. Как раз время таймаута и выбрано что бы отставшие все пакеты в сети удалились как мусорные.

OK, а можно ли изменить этот параметр? Оказывается в Windows есть целая ветка реестра, отвечающая за параметры tcp протокола.

HKEY_LOCAL_MACHINE \SYSTEM \CurrentControlSet \Services: \Tcpip \Parameters

Нас интересуют пока 2 параметра:

  1. TcpFinWait2Delay

  2. TcpTimedWaitDelay

Цель установить минимальное значение. Минимальное значение 30. Что означает что через 30 секунд порт опять будет доступен. Устанавливаем. Ну а далее наша стандартная проверка.

Запускаем сервер. Запускаем клиенты. Досим. Дожидаемся отказа socket. Потом запускаем консоль и вводим команду:

netstat -an

Такое ощущение что весь выделенный диапазон портов находится в состоянии TIME WAIT. Это пока соответствует ожиданиям. Ждём 30с. Повторяем команду. Листинг уменьшился в разы. Все порты с WAIT TIME пропали.

Проверяем серверный socket на оживление. Глух ((( А такие надежды были на эту настройку.

Впрочем, определённые выводы сделать можно. Настройка наша работает. Пауза 30с. Оставляем полезная настройка для нагруженного сервера.

Wireshark

Решил посмотреть, что нам покажет Wireshark. Кто не знает это мощнейший анализатор всевозможных протоколов, в том числе и tcp/ip. До недавнего времени в программе не была реализована функция прослушки localhost и приходилось производить некоторые танцы с бубном. Ставить виртуальную сетевую карту. Трафик пускать через неё. А Wireshark уже мог подключатся к прослушке этой карты.

Недавно подвезли возможность прослушки localhost что очень облегчило работу.

Далее всё стандартно. Запускаем сервер, клиентов. Убиваем слушающий socket.

Запускаем Wireshark ставим фильтр на 80 порт. Открываем браузер пытаемся закачать файл javascript.

Вот какое непотребство мы увидели в сниффере:

Анализируем увиденное.

Видно, наш запрос. Браузер с порта 36036 пытается достучатся к порту 80. Выставляет флаг SYN. Это стандартно. Но вот с порта 80 нам возвращается флаг RST оборвать соединения, сбросить буфер. Всё. И так по кругу.

Вывод. Wireshark нам особо не помощник. Разве только мы увидели, что слушающий socket не мёртв совсем, а отвечает. Он работает по какой-то своей внутренней логике, а не просто умер.

Журнал windows

Решил посмотреть может что в журнале событий windows есть что-то интересное. Для этого захожу в журнал.

Панель управления-> Администрирование-> Управление компьютером-> Служебные программы-> Просмотр событий-> Журналы Windows

Либо запустите eventvwr.msc

Там, разумеется, есть миллионы записей. И чтобы не копаться в этих миллионах, я делаю следующее. Убиваю socket. Захожу в журнал и чищу подразделы. Захожу в браузер пытаюсь скачать файл. Захожу в журнал и смотрю что там только что появилось.

К сожалению, никаких событий, кусающихся моей проблемы, не появилось. Я даже нашёл такой раздел Microsoft/Windows/TCPIP. Но кроме одинокой записи работает, там ничего не было.

Финал?

Поиск в интернете всей доступной информации по проблеме периодически выдавал мне страницы подобной этим:

И ещё во многих других местах.

Отсюда я сделала 2 вывода я со соей проблемой не одинок, и как минимум периодически с ней сталкиваются и другие.

Есть ещё 2 параметра TCP стека с которыми можно поэкспериментировать.

  1. TcpNumConnections - максимальное количество одновременных подключений в системе.

  2. TcpMaxDataRetransmissions - количество повторных посылок при неудаче.

Поиск по этим параметрам для Windows 10 ничего не дал. Только для Windows 2003-2008 Server. Может плохо искал (наверняка). Но я всё же решил их проверить.

Установил следующие значения:

TcpNumConnections REG_DWORD: 00fffffe (hex)

TcpMaxDataRetransmissions REG_DWORD: 00000005 (hex)

Перезагрузился. Повторил в который раз все процедуры. И.

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

В этом месте был завершающий текст с выводами и размышления на тему почему именно эти параметры помогли.

Но.

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

Эти параметры оказались бесполезны в решении нашей проблемы. Но я их всё-таки оставил потому как по смыслу они полезные.

Продолжаем.

Финал.

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

Удаленный хост принудительно разорвал существующее подключение.

В этом exсeption была паузу. С пояснением исключение на socket случай неординарный, например socket занят другим приложением, делаем паузу что бы всё утряслось и в лог не сыпалось миллионы сообщений.

Как оказалось не неординарный.

Картина сложилась.

Получается, что приёмный socket набрал большое число входящих подключений, которые оборвались, и больше не принимает новые подключения пока поток висел в exception на паузе.

Ради интереса провёл эксперимент. Убил socket. Ввёл команду netstat -an. И не увидел ни одного socket клиента на нашем socket сервера. Хотя в приёмном socket сервера висело куча мёртвых подключений.

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

Выводы.

Ну что ж. Я, конечно, ожидал что геройски одержу победу над некоей эпичной ошибкой. А всё оказалось очень банально. Собственно, как всегда.

Все эксперименты у меня заняли где-то полтора дня. Эту статью я писал намного больше.

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

Узнал новую информацию по поводу работы сети эти знания пригодятся впоследствии при решения очередного непонятного бага.

Почему у меня заняло так много времени на поиск банального Delay? Я думал над этим и пришёл к выводу, что все эксперименты уводили в сторону от ответа. Сложившееся картина показывала нам что умирал именно socket. Переставал принимать подключения. Спустя время оживал. Все сниферы показывали, что подключений не происходит.

Опять же было неправильное представление, что серверный socket работает в отдельном потоке. И в любом случае должен принимать клиентов. Поэтому я и скал проблему в настройках socket и tcp стека. Главная проблема я думал поток висит на socket. А в нашем аварийном случае он висел на exeption в Delay.

Подробнее..

Особенности тестирования Android без Google-сервисов

26.05.2021 16:17:30 | Автор: admin

Привет! Меня зовут Мария Лещинская, я QA-специалист в Surf. Наша компания разрабатывает мобильные приложения с 2011 года. В этом материале поговорим о тестировании устройств Android, на которых нет поддержки Google Services.

Huawei без Google-сервисов начали массово выпускаться в 2019 году. Мы в Surf, разумеется, задумались о будущем: как сильно пострадают наши процессы и что нужно незамедлительно осваивать.

Я поделюсь впечатлениями от работы с Android без Google-сервисов и расскажу, какие возможности имеют такие мобильные устройства при тестировании.

В начале статьи общая информация про AppGallery и AppGallery Connect. Если вы всё это уже знаете, переходите сразу к сути к особенностям тестирования Android-платформы c поддержкой Huawei без Google-сервисов.

Что такое AppGallery, AppGallery Connect и почему Huawei без поддержки Google

Приложения под iOS- и Android-платформы можно встретить в официальных магазинах AppStore и Google Play. Туда мы идём в первую очередь, когда хотим установить новое мобильное приложение на телефон.

С 2018 года во всем мире (а в Китае ещё раньше) появился другой магазин мобильных приложений AppGallery, а с ним и AppGallery Connect.

AppGallery это менеджер пакетов и платформа распространения приложений, разработанная Huawei для операционной системы Android. AppGallery Connect универсальная платформа для поддержки всего жизненного цикла приложения: разработки, распространения, управления, тестирования и анализа.

За AppGallery последовал и выпуск устройств на базе Huawei: с 2019 года для нихв принципе отсутствует возможность работать с Google-сервисами, поэтому работа с Android стала сложнее. Нужно было оперативно включиться в работу и придумать, какизменить процессы тестирования платформы.

Казалось бы, зачем менять процессы и подстраиваться под ещё один магазин? Как много багов может добавиться к уже существующим? Стоит ли тратить время и деньги? У нас есть два аргумента.

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

Во-вторых, у AppGallery солидное количество пользователей. Магазин появился в 2018 году, к октябрю 2020 года приложение доступно в 170 странах мира, число уникальных пользователей 700 миллионов человек. Как говорит статистика, ежемесячная аудитория составляет 490 миллионов активных пользователей.

При этом для Huawei написали всего 96 000 приложений. Для сравнения в Play Store 2.9 миллиона приложений: это значит что более двух с половиной миллионов приложений отсутствуют в AppGallery.

В AppGallery нет, например, Instagram, Facebook и WhatsАpp. Их, конечно, можно скачать и установить вручную без ограничений: найти по отдельности в браузере или через какой-нибудь агрегатор. Также в сети появились сервисы, с помощью которых можно скачать самые популярные приложения. Но не каждый пользователь захочет выполнять дополнительные манипуляции.

Три вида Android-устройств

Как только кAndroidс Google-сервисами прибавились Huawei с сервисами HMS, некоторые устройства стали автоматически поддерживать оба вида сервисов (например, как Huawei до 2020 года выпуска).

Ниже представлено сравнение трёх типов устройств Android: с Google-cервисами, без них и с поддержкой обоих.

Android без Google-сервисов

Android только с Google-сервисами

Android с поддержкой Google-сервисов и App Gallery

.apk/.aab

Установочный файл может быть один на все виды устройств Android, или их может быть два: отдельно с сервисами Huawei и отдельно с сервисами Google.

Мы в Surf обычно делаем один .apk/.aab для обоих видов устройств Android. Логика работы приложений на разных устройствах определена внутри сборки. Но также могут быть два установочных файла одного приложения: один идёт в Google play, другой в AppGallery.

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

Проще и быстрее сделать одну сборку, чем две, но как только подключается CI, разница минимизируется. Использование двух сборок теоретически может уменьшить вес приложения особенно если в нем используются разные фреймворки для реализации фич на Android с Google Services и без.

инструменты

AppGallery Connect и сервисы, не использующие Google.

Сервисы, использующие google, в том числе Firebase.

AppGallery Connect, сервисы, не использующие Google, и сервисы, использующие Google в том числе Firebase.

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

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

баги

Баги по общей логике приложения, конечно, будут распространяться на оба вида устройств. Существенные отличия могут появиться при работе сервисов типа Google Pay, push-уведомлений, deep links и dynamic links и так далее.

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

Возможности тестирования через AppGallery Connect

Проблемы начинаются при использовании разных библиотек на двух видах устройств Android. Мы в Surf пользуемся различными сервисами для работы с push-уведомлениями, аналитикой, dynamic или deep links, performance-мониторингом. Поэтому когда стали брать на вооружение работу с Huawei без Google-сервисов, волновались, насколько сильно изменится работа QA: получится ли тестировать push-уведомления и dynamic links в привычном ритме или придётся адаптироваться к абсолютно новому процессу? К счастью, сам процесс меняется несильно. Но есть вещи, о которых необходимо знать, прежде чем браться за работу с устройствами без поддержки Google.

Huawei без Google-сервисов не имеет доступа к инструментам, которые работают с Google, например, Firebase. Сервисы для тестирования и работы мобильного приложения нужно настраивать через AppGallery (к счастью, AppGallery Connect имеет базовые возможности из коробки) или другие доступные инструменты. А возможно, придумывать и свои решения.

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

Аналитика

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

Отладка приложения (App Debugging) позволяет смотреть события, приходящие от МП, и их параметры в реальном времени. Чтобы подключить устройство к Отладке приложения в AppGallery Connect, нужно подключить устройство к компьютеру и в терминале выполнить команду:

adb shell setprop debug.huawei.hms.analytics.app <package_name>

Чтобы отключить устройство от отладки, выполнить команду:

adb shell setprop debug.huawei.hms.analytics.app .none.

Чтобы быстрее найти <package_name>, можно воспользоваться командой adb:

adb shell pm list packages

Общий сбор аналитики в AppGallery Connect тоже доступен. Он называется Просмотр в реальном времени (аналогично Events в Firebase)

Просмотр в реальном времени (Real-Time Overview) собирает все события с МП в одном месте. Можно строить графики по выбранным критериям, активировать фильтры и в целом проводить анализ по мобильному приложению.

Удалённая настройка и параметры

Удалённая настройка (Remote Configuration) позволяет управлять различными параметрами для приложения, и при необходимости обращаться к AppGallery Connect прямо из МП для работы с ними (аналогично RemoteConfig в Firebase).

Push-уведомления

При работе с push-уведомлениями Surf использует разные инструменты: Flocktory, Mindbox, Firebase и другие. Не все инструменты пока ещё могут работать с Android без поддержки Google, но базовая возможность подключить push-уведомления для Huawei есть: это их фирменная реализация через AppGallery Connect. Настройка пyшей происходит в PushKit.

Важно отметить, что пушер бэкэнда обязательно должен уметь взаимодействовать с AppGallery PushKit. Иначе push-уведомления придётся отправлять вручную из AppGallery Connect.

App Linking

App Linking сервис для работы с dynamic links. На основании deep links App Linking предоставляет пользователям доступ к нужному контенту непосредственно на веб-страницах и в мобильных приложениях: это повышает конверсию пользователей (аналогично Dynamic Links в Firebase).

Dynamic Links vs Deep Links

Dynamic links это интеллектуальные URL-адреса, которые позволяют отправлять существующих и потенциальных пользователей в любое место в приложении iOS или Android. Dynamic links легко переводят пользователей с любого URL на контент в приложении. Если пользователь не установил приложение на устройство, он увидит контент, когда установит его.

Deep Links это тип локальных ссылок: они направляют пользователей непосредственно в приложение и только. Соответственно, если приложение не установлено, работа с deep links невозможна.

Простыми словами, dynamic links ссылки, которые могут редиректить пользователя откуда угодно прямо в приложение или магазин, если оно не установлено (а после установки и в приложение). Deep links ссылки, которые привязаны к конкретному экрану внутри приложения и работают локально внутри МП.

На данный момент при работе с AppGallery Connect нет возможности создавать кастомные dynamic links: например, которые были бы одинаковыми и для обоих видов устройств Android (с поддержкой Google и без). Но c deep links всё в порядке.

Crash

Чтобы ловить незаметные с первого взгляда баги, стоит мониторить crash-аналитику даже на debug-версиях. Это необходимо, когда приложение потенциально разрабатывается на большую аудиторию и релиз близко не говоря уже о ситуации, когда МП уже доступно магазине и им пользуется много людей.

Нам было важно, чтобы такой инструмент был доступен и для Huawei без Google-сервисов. Crash плагин, позволяющий отслеживать и анализировать баги, краши и ошибки в приложении (аналогично Crashlytics в Firebase).

APM

Чтобы обеспечить качество клиент-серверного взаимодействия, удобно использовать инструмент, который бы помогал анализировать ответы от сервера и отрисовку экранов и элементов в приложении. В AppGallery Connect такой инструмент APM. Это сервис, который помогает искать и устранять проблемы производительности приложения и улучшать таким образом пользовательский интерфейс (аналогично Performance в Firebase).

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

Особенности тестирования Android-платформы c поддержкой Huawei без Google-сервисов

В первое время при работе с устройствами Huawei без Google-сервисов мы тратили много времени на анализ и выстраивание процессов. Сейчас всё наладилось.

В целом можно выделить следующие проблемы и решения.

Шаринг сборок

На проектах мы часто шарим сборки через Firebase, или напрямую скачиваем .apk из Jenkins, или собираем вручную из Android Studio. Проблем со скачиванием или ручной установкой .apk для Huawei без Google-сервисов нет. Проблем с App Tester приложением Firebase для шаринга сборок тоже нет. Использовать непосредственно приложение не получится, но пройти по invite из почты в браузер для скачивания сборки удастся.

Лайфхак: сохраняйте страницу из браузера на рабочий стол телефона и не знайте горя.

Устройства

Конечно, для тестирования необходимы устройства и эмуляторы без Google-сервисов.

  • Если на проекте планируется адаптация под AppGallery, можно отправить заявку Huawei. Они пришлют девайсы для тестирования.Правда, финальное слово всегда за самим Huawei: отправка запроса ничего не гарантирует. Но опция приятная.

  • Неплохой вариант использовать эмуляторы. Конечно, они не восполнят полную работу с устройством, но могут помочь в отдельных случаях.

На что обратить внимание

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

1. Push-уведомления. На Huawei без Google-сервисов не будут работать push-уведомления, реализованные на backend через Firebase (а такое встречается сейчас часто). Такие устройства имеют свой hms-токен, и для работы с ними нужна специальная реализация.

2. Dynamic links. Инструмент AppGallery Connect не поддерживает кастомный формат dynamic links, поэтому нельзя настроить унифицированную ссылку на оба вида конфигураций устройств Android. Решение: использовать deep links или другой инструмент по работе с ссылками, работающий без Google Services.

3. Библиотеки с Google-сервисами. Различия в реализации и потенциальное скопление багов в логике будут, если в проекте используются библиотеки с Google-сервисами. Для Huawei их придётся заменить на другие фреймворки или if-ответвления. Тогда понадобится более тщательно тестировать оба вида устройств.

  • Google Pay. На Android без Google-сервисов можно столкнуться с окном Оплата недоступна, так как для нее нужен доступ к Google-сервисам, которые ваше устройство не поддерживает. Аналогичную ошибку можно встретить при запуске других приложений, не предназначенных для Huawei без Google.

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

  • Google-аккаунт. Авторизация через Google-аккаунт на Huawei без поддержки Google недоступна. Но реализация авторизации-регистрации через Huawei-аккаунт была бы кстати.

  • Магазины. Если мобильное приложение может отправить пользователя в магазин (для оценки, например), то необходимо проверить, что Android без Google Services отправляет в App Gallery, а Android с поддержкой Google в Google Play. Если устройство поддерживает обе конфигурации, было бы здорово, если бы пользователь мог выбрать между магазинами.

4. Сервисы с поддержкой Google. Для Huawei без Google придётся найти аналоги или разработать их самостоятельно. Хорошо, что важные базовые инструменты, как упоминалось выше, доступны в AppGallery Connect из коробки.

Например, на Android-устройствах можно открывать ссылки из приложения тремя разными способами:

  • WebView,

  • CustomTabs (разработка Google),

  • браузер.

Для Huawei без поддержки Google, по умолчанию доступны только два способа или дополнительная разработка вручную.

Android с Google и без: сколько времени понадобится на тестирование

Базовые активности QA:

  • планирование,

  • ревью ТЗ и дизайна,

  • написание проверок,

  • прогоны по фиче, итоговые, регрессионные,

  • написание отчётности

Это пример активностей QA в среднем по больнице. Мы исключаем особенности компании и проектов и говорим немного в вакууме.

При работе с Huawei без Google-сервисов точно добавляется время к каждой из активностей:

  • Ревью ТЗ и дизайна, написание проверок. Будут дополнительные кейсы, отражающие особенности работы с такими устройствами. Можно смело увеличивать оценку временных затрат на это в 1,41,6 раза. Здесь время уйдёт либо на обработку дополнительных сценариев в ТЗ и дизайне, либо на анализ и подтверждение, что никакой особенной реализации для Android без Google-сервисов нет.

  • Прогоны. Во время прогонов (по фиче, итоговых, регрессионных) рекомендуется проводить тестирования как на Android с Google-сервисами, так и без. Особое внимание устройствам, где доступны оба вида сервисов. Здесь сокращение количества устройств может когда-нибудь неприятно выстрелить. Время может увеличиться в 1,82 раза и уйдёт на осуществление прогона на всех трёх видах устройств.

  • Обратная связь. Под обратной связью мы в Surf подразумеваем просмотр маленьких задач (которые не требует прогона по фиче например, Смену статичного текста) и исправленных багов. При работе с обратной связью, а также при анализе и просмотре импакта от багов и прочих задач, снова не стоит забывать про тот же список устройств (без и с Google-сервисами, а также с двумя видами сервисов) для тестирования. Время увеличивается примерно в 1,3 раза снова для того, чтобы осуществить ретест или проверку задачи на этих видах устройств.

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

  • Тестируемые устройства. И, конечно, на время может повлиять количество выбранных устройств для тестирования (автоматизированного или ручного). Подходить к выбору устройств стоит ответственно, проанализировав множественные факторы проекта и аудитории.

При тестировании на Android с Google Services хочется покрыть наибольшее количество устройств: разные операционные системы, оболочки, разрешения экранов, внутренние особенности и возможности. Устройств становится ещё больше, когда добавляются девайсы Huawei с HMS-сервисами.

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

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

Время общего тестирования фичи увеличится:

  • в 1,82 раза в случае разных инструментов для реализации фичи;

  • в 1,31,5 раза в случае одного инструмента для реализации фичи (в том числе при отсутствии отличий на первый взгляд);

  • в 1,41,6 раза в случае дополнительных требований и отличительной части реализации.

Таблица-сравнение по тестированию фичи для устройств с Google и без

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

Фича

Поддержка Android только с сервисами Google

Поддержка Android с Huawei и Google Services

Авторизация по логину и паролю, а также через соц.сети

X

1,5X

Push-уведомления (реализация через Firebase и AppGallery Connect)

X

1,8X

Аналитика, около 15 событий (реализация через Firebase и AppGallery Connect)

X

2X

Аналитика, около 15 событий (реализация через один сервис, например, Amplitude)

X

1,4X

Значение Х время на тестирование Android с Google-сервисами. Оценка Y*X время на тестирование Android с двумя видами сервисов, где Y коэффициент увеличения времени на работу с Huawei с HMS-сервисами

Все временные оценки исходя из нашего опыта. Приводим их для примерного понимания.

Что хотелось бы сказать в конце...

Мы в Surf поддерживаем устройства Huawei без Google-сервисов на некоторых проектах и довольны процессами. Конечно, поработать головой иногда приходится чуть дольше: разработчикам чтобы найти универсальное решение. QA чтобы найти максимальное количество дефектов, широко покрыть фичи проверками и обеспечить качественное тестирование. И на мой взгляд, оно того стоит.

Подробнее..

Тестирование в Apache Spark Structured Streaming

02.01.2021 20:04:09 | Автор: admin

Введение


На текущий момент не так много примеров тестов для приложений на основе Spark Structured Streaming. Поэтому в данной статье приводятся базовые примеры тестов с подробным описанием.


Все примеры используют: Apache Spark 3.0.1.


Подготовка


Необходимо установить:


  • Apache Spark 3.0.x
  • Python 3.7 и виртуальное окружение для него
  • Conda 4.y
  • scikit-learn 0.22.z
  • Maven 3.v
  • В примерах для Scala используется версия 2.12.10.

  1. Загрузить Apache Spark
  2. Распаковать: tar -xvzf ./spark-3.0.1-bin-hadoop2.7.tgz
  3. Создать окружение, к примеру, с помощью conda: conda create -n sp python=3.7

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


SPARK_HOME=/Users/$USER/Documents/spark/spark-3.0.1-bin-hadoop2.7PYTHONPATH=$SPARK_HOME/python:$SPARK_HOME/python/lib/py4j-0.10.9-src.zip;

Тесты


Пример с scikit-learn


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


Для написания тестов будет использоваться следующий пример: LinearRegression.


Итак, пусть код для тестирования использует следующий "шаблон" для Python:


class XService:    def __init__(self):        # Инициализация    def train(self, ds):        # Обучение    def predict(self, ds):        # Предсказание и вывод результатов

Для Scala шаблон выглядит соответственно.


Полный пример:


from sklearn import linear_modelclass LocalService:    def __init__(self):        self.model = linear_model.LinearRegression()    def train(self, ds):        X, y = ds        self.model.fit(X, y)    def predict(self, ds):        r = self.model.predict(ds)        print(r)

Тест.


Импорт:


import unittestimport numpy as np

Основной класс:


class RunTest(unittest.TestCase):

Запуск тестов:


if __name__ == "__main__":    unittest.main()

Подготовка данных:


X = np.array([    [1, 1],  # 6    [1, 2],  # 8    [2, 2],  # 9    [2, 3]  # 11])y = np.dot(X, np.array([1, 2])) + 3  # [ 6  8  9 11], y = 1 * x_0 + 2 * x_1 + 3

Создание модели и обучение:


service = local_service.LocalService()service.train((X, y))

Получение результатов:


service.predict(np.array([[3, 5]]))service.predict(np.array([[4, 6]]))

Ответ:


[16.][19.]

Все вместе:


import unittestimport numpy as npfrom spark_streaming_pp import local_serviceclass RunTest(unittest.TestCase):    def test_run(self):        # Prepare data.        X = np.array([            [1, 1],  # 6            [1, 2],  # 8            [2, 2],  # 9            [2, 3]  # 11        ])        y = np.dot(X, np.array([1, 2])) + 3  # [ 6  8  9 11], y = 1 * x_0 + 2 * x_1 + 3        # Create model and train.        service = local_service.LocalService()        service.train((X, y))        # Predict and results.        service.predict(np.array([[3, 5]]))        service.predict(np.array([[4, 6]]))        # [16.]        # [19.]if __name__ == "__main__":    unittest.main()

Пример с Spark и Python


Будет использован аналогичный алгоритм LinearRegression. Нужно отметить, что Structured Streaming основан на тех же DataFrame-х, которые используются и в Spark Sql. Но как обычно есть нюансы.


Инициализация:


self.service = LinearRegression(maxIter=10, regParam=0.01)self.model = None

Обучение:


self.model = self.service.fit(ds)

Получение результатов:


transformed_ds = self.model.transform(ds)q = transformed_ds.select("label", "prediction").writeStream.format("console").start()return q

Все вместе:


from pyspark.ml.regression import LinearRegressionclass StructuredStreamingService:    def __init__(self):        self.service = LinearRegression(maxIter=10, regParam=0.01)        self.model = None    def train(self, ds):        self.model = self.service.fit(ds)    def predict(self, ds):        transformed_ds = self.model.transform(ds)        q = transformed_ds.select("label", "prediction").writeStream.format("console").start()        return q

Сам тест.


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


train_ds = spark.createDataFrame([    (6.0, Vectors.dense([1.0, 1.0])),    (8.0, Vectors.dense([1.0, 2.0])),    (9.0, Vectors.dense([2.0, 2.0])),    (11.0, Vectors.dense([2.0, 3.0]))],    ["label", "features"])

Это очень удобно и код получается компактным.


Но подобный код, к сожалению, не будет работать в Structured Streaming, т.к. созданный DataFrame не будет обладать нужными свойствами, хотя и будет соответствовать контракту DataFrame.
На текущий момент для создания источников для тестов можно использовать такой же подход, что и в тестах для Spark.


def test_stream_read_options_overwrite(self):    bad_schema = StructType([StructField("test", IntegerType(), False)])    schema = StructType([StructField("data", StringType(), False)])    df = self.spark.readStream.format('csv').option('path', 'python/test_support/sql/fake') \        .schema(bad_schema)\        .load(path='python/test_support/sql/streaming', schema=schema, format='text')    self.assertTrue(df.isStreaming)    self.assertEqual(df.schema.simpleString(), "struct<data:string>")

И так.


Создается контекст для работы:


spark = SparkSession.builder.enableHiveSupport().getOrCreate()spark.sparkContext.setLogLevel("ERROR")

Подготовка данных для обучения (можно сделать обычным способом):


train_ds = spark.createDataFrame([    (6.0, Vectors.dense([1.0, 1.0])),    (8.0, Vectors.dense([1.0, 2.0])),    (9.0, Vectors.dense([2.0, 2.0])),    (11.0, Vectors.dense([2.0, 3.0]))],    ["label", "features"])

Обучение:


service = structure_streaming_service.StructuredStreamingService()service.train(train_ds)

Получение результатов. Для начала считываем данные из файла и выделяем: признаки и идентификатор для объектов. После запускаем предсказание с ожиданием в 3 секунды.


def extract_features(x):    values = x.split(",")    features_ = []    for i in values[1:]:        features_.append(float(i))    features = Vectors.dense(features_)    return featuresextract_features_udf = udf(extract_features, VectorUDT())def extract_label(x):    values = x.split(",")    label = float(values[0])    return labelextract_label_udf = udf(extract_label, FloatType())predict_ds = spark.readStream.format("text").option("path", "data/structured_streaming").load() \    .withColumn("features", extract_features_udf(col("value"))) \    .withColumn("label", extract_label_udf(col("value")))service.predict(predict_ds).awaitTermination(3)

Ответ:


15.9669918.96138

Все вместе:


import unittestimport warningsfrom pyspark.sql import SparkSessionfrom pyspark.sql.functions import col, udffrom pyspark.sql.types import FloatTypefrom pyspark.ml.linalg import Vectors, VectorUDTfrom spark_streaming_pp import structure_streaming_serviceclass RunTest(unittest.TestCase):    def test_run(self):        spark = SparkSession.builder.enableHiveSupport().getOrCreate()        spark.sparkContext.setLogLevel("ERROR")        # Prepare data.        train_ds = spark.createDataFrame([            (6.0, Vectors.dense([1.0, 1.0])),            (8.0, Vectors.dense([1.0, 2.0])),            (9.0, Vectors.dense([2.0, 2.0])),            (11.0, Vectors.dense([2.0, 3.0]))        ],            ["label", "features"]        )        # Create model and train.        service = structure_streaming_service.StructuredStreamingService()        service.train(train_ds)        # Predict and results.        def extract_features(x):            values = x.split(",")            features_ = []            for i in values[1:]:                features_.append(float(i))            features = Vectors.dense(features_)            return features        extract_features_udf = udf(extract_features, VectorUDT())        def extract_label(x):            values = x.split(",")            label = float(values[0])            return label        extract_label_udf = udf(extract_label, FloatType())        predict_ds = spark.readStream.format("text").option("path", "data/structured_streaming").load() \            .withColumn("features", extract_features_udf(col("value"))) \            .withColumn("label", extract_label_udf(col("value")))        service.predict(predict_ds).awaitTermination(3)        # +-----+------------------+        # |label|        prediction|        # +-----+------------------+        # |  1.0|15.966990887541273|        # |  2.0|18.961384020443553|        # +-----+------------------+    def setUp(self):        warnings.filterwarnings("ignore", category=ResourceWarning)        warnings.filterwarnings("ignore", category=DeprecationWarning)if __name__ == "__main__":    unittest.main()

Нужно отметить, что для Scala можно воспользоваться созданием потока в памяти.
Это может выглядеть вот так:


implicit val sqlCtx = spark.sqlContextimport spark.implicits._val source = MemoryStream[Record]source.addData(Record(1.0, Vectors.dense(3.0, 5.0)))source.addData(Record(2.0, Vectors.dense(4.0, 6.0)))val predictDs = source.toDF()service.predict(predictDs).awaitTermination(2000)

Полный пример на Scala (здесь, для разнообразия, не используется sql):


package aaa.abc.dd.spark_streaming_pr.clusterimport org.apache.spark.ml.regression.{LinearRegression, LinearRegressionModel}import org.apache.spark.sql.DataFrameimport org.apache.spark.sql.functions.udfimport org.apache.spark.sql.streaming.StreamingQueryclass StructuredStreamingService {  var service: LinearRegression = _  var model: LinearRegressionModel = _  def train(ds: DataFrame): Unit = {    service = new LinearRegression().setMaxIter(10).setRegParam(0.01)    model = service.fit(ds)  }  def predict(ds: DataFrame): StreamingQuery = {    val m = ds.sparkSession.sparkContext.broadcast(model)    def transformFun(features: org.apache.spark.ml.linalg.Vector): Double = {      m.value.predict(features)    }    val transform: org.apache.spark.ml.linalg.Vector => Double = transformFun    val toUpperUdf = udf(transform)    val predictionDs = ds.withColumn("prediction", toUpperUdf(ds("features")))    predictionDs      .writeStream      .foreachBatch((r: DataFrame, i: Long) => {        r.show()        // scalastyle:off println        println(s"$i")        // scalastyle:on println      })      .start()  }}

Тест:


package aaa.abc.dd.spark_streaming_pr.clusterimport org.apache.spark.ml.linalg.Vectorsimport org.apache.spark.sql.SparkSessionimport org.apache.spark.sql.execution.streaming.MemoryStreamimport org.scalatest.{Matchers, Outcome, fixture}class StructuredStreamingServiceSuite extends fixture.FunSuite with Matchers {  test("run") { spark =>    // Prepare data.    val trainDs = spark.createDataFrame(Seq(      (6.0, Vectors.dense(1.0, 1.0)),      (8.0, Vectors.dense(1.0, 2.0)),      (9.0, Vectors.dense(2.0, 2.0)),      (11.0, Vectors.dense(2.0, 3.0))    )).toDF("label", "features")    // Create model and train.    val service = new StructuredStreamingService()    service.train(trainDs)    // Predict and results.    implicit val sqlCtx = spark.sqlContext    import spark.implicits._    val source = MemoryStream[Record]    source.addData(Record(1.0, Vectors.dense(3.0, 5.0)))    source.addData(Record(2.0, Vectors.dense(4.0, 6.0)))    val predictDs = source.toDF()    service.predict(predictDs).awaitTermination(2000)    // +-----+---------+------------------+    // |label| features|        prediction|    // +-----+---------+------------------+    // |  1.0|[3.0,5.0]|15.966990887541273|    // |  2.0|[4.0,6.0]|18.961384020443553|    // +-----+---------+------------------+  }  override protected def withFixture(test: OneArgTest): Outcome = {    val spark = SparkSession.builder().master("local[2]").getOrCreate()    try withFixture(test.toNoArgTest(spark))    finally spark.stop()  }  override type FixtureParam = SparkSession  case class Record(label: Double, features: org.apache.spark.ml.linalg.Vector)}

Выводы


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


Такие абстракции как DataFrame позволяют это сделать легко и просто.


При использовании Python данные придется хранить в файлах.


Ссылки и ресурсы


Подробнее..
Категории: Scala , Python , Testing , Apache , Spark , Apache spark , Kafka , Streaming

Почему большинство юнит тестов пустая трата времени? (перевод статьи)

17.05.2021 16:18:04 | Автор: admin

Автор: James O Coplien

Перевод: Епишев Александр

1.1 Наши дни

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

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

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

Классы превратились в объекты анализа, и, в определенной степени, проектирования. Популярной техникой дизайна стали CRC-карты (обычно представляющие Классы, Ответственности и Кооперацию), каждый класс в которых представлен отдельным человеком. Объектная ориентация начала ассоциироваться с антропоморфным дизайном. Классы, к тому же, превратились в единицы администрирования, дизайна и программирования, а их антропоморфная суть усилила стремление каждого создателя класса его протестировать. Поскольку у некоторых методов класса сохранилась такая же контекстуализация, как и у функции FORTRAN, у программистов возникла необходимость предоставлять контекст перед выполнением метода (помните, что мы не тестируем классы и, даже, не тестируем тестовые объекты, единицей функционального теста является метод). Юнит тесты обеспечивали выполнение сценариев драйверами. Моки - контекст состояния окружения (энва) и других методов, от которых зависел тестируемый метод. При подготовке к тесту, тестовые окружения поставляли необходимые средства для создания каждого объекта в его правильном состоянии.

1.2 Лекарство хуже болезни

Конечно же, юнит-тестирование не является проблемой исключительно объектно-ориентированного программирования, de rigueur (лат. "крайней необходимостью"), скорее всего, его сделала комбинация объектной-ориентированности, эджайла, разработки программного обеспечения, а также рост инструментов и вычислительных мощностей. Как консультант, я часто слышу вопросы о юнит-тестировании, включая следующий от одного из своих клиентов, Ричарда Якобса (Richard Jacobs) из Sogeti (Sogeti Nederland B.V.):

Второй вопрос касается юнит-тестов. Если я правильно припоминаю, вы говорили, что юнит-тесты - это пустая трата времени. Во-первых, я был удивлен. Тем не менее, сегодня моя команда сообщила, что их тесты сложнее, чем сам код. (Это не та команда, которая изначально написала код и юнит тесты. Поэтому некоторые тесты застают их врасплох. Текущая команда более высоко квалифицирована и дисциплинирована.) В таком случае, по моему, это пустая трата времени... Когда я ежедневно программировал, то создавал действительно тестируемый код, однако, почти никогда не писал никакие юнит тесты. При этом я заслуживал признание за свой качественный код и почти безошибочное программное обеспечение. Мне хотелось бы разобраться, ПОЧЕМУ такой вариант работал в моем случае?

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

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

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

(Здесь, триллион - не риторический прием, а цифра, основанная на различных возможных состояниях, с учетом того, что средний размер объекта представляет собой четыре слова, и, по консервативной оценке, вы используете 16-битные слова).

1.3 Тесты ради тестов и спроектированные тесты

У меня был клиент из Северной Европы, разработчики которого должны были предоставить 40% покрытия кода, для, так называемого, 1-го уровня зрелости программного обеспечения, 60% для 2-го уровня и 80% для 3-го, хотя были и стремящиеся к 100%. Без проблем! Как вы могли бы предположить, достаточно сложная процедура с ветвлениями и циклами стала бы вызовом, однако, это всего лишь вопрос принципа divide et impera (разделяй и властвуй). Большие функции, для которых 80% покрытие было невозможным, разбивались на множество более мелких, для которых 80% уже было тривиальным. Такой подход повысил общий корпоративный показатель зрелости команд всего лишь за один год, потому как вы обязательно получаете то, что поощряете. Конечно же, это также означало, что функции больше не инкапсулировали алгоритмы. Невозможным оказалось понимание контекста выполняемой строки, точнее тех, которые предшествуют и следуют за ней во время выполнения, поскольку эти строки кода больше не имеют прямого отношения к той, которая нас интересует. Такой переход в последовательности теперь происходил благодаря вызову полиморфной функции - гипер-галактической GOTO. Даже если всё, что вас беспокоит, - это покрытие решений (branch coverage), это больше не имеет значения.

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

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

Задумайтесь на секунду о вычислительной сложности этой задачи. Под 100% покрытием, я подразумеваю проверку всех возможных комбинаций всех возможных ветвлений, проходящих через все методы класса, которые воспроизводят все возможные конфигурации битов данных, доступные этим методам, в каждой инструкции машинного языка во время выполнения программы. Все остальное - это эвристика, о корректности которой нельзя сделать никаких формальных заявлений. Число возможных путей выполнения с помощью функции невелико: скажем, 10. Перекрестное произведение этих путей с возможными конфигурациями состояний всех глобальных данных (включая данные экземпляра, которые для области видимости метода являются глобальными) и формальных параметров в действительности же очень велико. Перекрестное произведение этого числа с возможной последовательностью методов внутри класса представляется счетно-бесконечным. Если вы возьмете несколько типичных чисел, то быстро осознаете, насколько вам повезло, если получите покрытие лучше, чем 1 из 1012.

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

Если вы пишете тест с целью покрытия как можно большего количества возможных сценариев, тогда можете уже выделять ферму машин для прогона тестов и отслеживания последних результатов 24/7.

Помните, однако, что автоматизированный хлам - это всё ещё хлам. И те из вас, у кого есть корпоративная Lean-программа, могли заметить, что основы производственной системы Toyota, которые лежали в основе Scrum, очень сильно противились автоматизации интеллектуальных задач (http://personeltest.ru/away/www.computer.org/portal/web/buildyourcareer/Agile Careers/-/blogs/autonomation). Более эффективно - это постоянно удерживать человека процессе, что становится еще более очевидным при исследовательском тестировании. Если вы собираетесь что-то автоматизировать, автоматизируйте что-нибудь ценное. Автоматизировать необходимо рутинные вещи. Возможно даже, вы получите еще больше прибыли от инвестиций, если автоматизируете интеграционные тесты, тесты для проверки регрессионных багов, а также системные, вместо того, чтобы заниматься автоматизацией юнит тестов.

Более разумный подход уменьшает объем тестового кода за счет формального проектирования тестов: то есть, формальной проверки граничных условий, большего количества тестов белого-ящика и т.д. Для этого необходимо, чтобы программный юнит проектировался как тестируемый. Вот как это делают инженеры по аппаратному обеспечению: разработчики предоставляют контрольные точки, способные считывать значения c J-Tag микросхем, для доступа к внутренним значениям сигналов микросхем - это равносильно доступу к значениям между промежуточными вычислениями, содержащимися в вычислительном юните. Я настоятельно рекомендую делать подобное на системном уровне, на котором должно быть сосредоточено основное внимание тестирования; я никогда не видел, чтобы кто-то достигал подобного на уровне юнита. Без таких приемов вы ограничиваете себя юнит-тестированием черного ящика.

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

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

1.4 Убеждение, что тесты умнее кода, говорит о скрытом страхе или плохом процессе

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

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

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

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

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

Тем не менее, будем честны, ошибки будут всегда. Тестирование никуда не денется.

1.5 У тестов с низким уровнем риска низкая (даже потенциально отрицательная) отдача

Как то я озвучил своему клиенту предположение о том, что множество их тестов могут быть тавтологическими. Предположим, задача какой-то функции - это присвоение X значения 5, и я готов поспорить, что существует соответствующий тест для данной функции, который, после запуска, проверяет, равняется ли X 5. Снова же, хорошее тестирование, основывается на тщательном размышлении, а также базовых принципах управления рисками. Управление рисками строится на статистике и теории информации; если тестировщики (или, по крайней мере, менеджер по тестированию) не обладают хотя бы элементарными навыками в этой области, вы, с большой вероятностью, создаете множество бесполезных тестов.

Разберем тривиальный пример. Цель тестирования - предоставить информацию о вашей программе. (Тестирование само по себе не повышает качество; это делают программирование и проектирование. Тестирование лишь сообщает об упущениях команды в создании правильного проектирования и соответствующей реализации.) Большинство программистов хотят услышать информацию о том, что их программный компонент работает. Поэтому, как только в проекте трехлетней давности была создана первая функция, тут же для нее был написан и юнит тест. Тест ни разу не падал. Вопрос: Много ли информации содержится в этом тесте? Другими словами, если 1 - это успешно выполненный тест, а 0 - упавший, тогда сколько будет информации в следующей строке результатов:

11111111111111111111111111111111

Существует несколько возможных ответов, обусловленных видом применяемого формализма, хотя большинство из них не верны. Наивный ответ - 32, однако, это биты данных, а не информации. Возможно, вы информационный теоретик и скажете, что количество битов информации в однородной двоичной строке равносильно двоичному логарифму длины этой строки, которая в данном случае равна 5. Однако это не то, что я хочу знать: в конце концов хотелось бы понять, сколько информации можно получить после одноразового прогона такого теста. Информация основывается на вероятности. Если вероятность успешного прохождения теста равняется 100%, тогда, по определению теории информации, этой информации нет вообще. Ни в одной из единиц указанной выше строки не содержится почти никакой информации. (Если бы строка была бесконечно длинной, то в каждом тестовом прогоне было бы ровно ноль битов информации.)

Далее, сколько бит информации в следующей строке тестовых прогонов?

1011011000110101101000110101101

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

00000000000000000000000000000000

в которой, фактически, нет никакой информации, в том числе, даже о процессе улучшения качества.)

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

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

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

Если у вас есть подобные тесты - это второй претендент на удаление.

Третий набор для удаления - тавтологические тесты. Я сталкиваюсь с ними чаще, чем вы можете себе представить, особенно среди последователей, так называемой, разработки через тестирование (TDD). (Кстати, проверка this на ненулевое/не пустое (non-null) значение при входе в метод, не является тавтологической, и может быть очень информативной. Однако, как и в случае с большинством юнит тестов, лучше сделать ассершн, чем пичкать свой тестовый фреймворк подобными проверками. Подробнее об этом ниже.)

Во многих компаниях, единственные тесты с бизнес-ценностью - это те, в основании которых лежат бизнес-требования. Большинство же юнит тестов основываются на фантазиях программистов о том, как должна работать функция: на их надеждах, стереотипах, а иногда и желаниях, как все должно было бы быть. У всего этого нет подтвержденной ценности. В 1970-х и 1980-х годах существовали методологии, опирающиеся на прослеживаемость (tracebility), и стремящиеся сократить системные требования вплоть до уровня юнитов. В общем, это NP-трудная (нелинейная полиномиальная) задача (если только вы не выполняете чисто процедурную декомпозицию), поэтому я очень скептичен в отношении всех, кто говорит, что способен её решить. В итоге, единственный вопрос, который следовало бы задавать каждому тесту: Если тест упадет, какое из бизнес-требований будет нарушено? В большинстве случаев, ответ: Я не знаю. Если вы не понимаете ценность теста, тогда, теоретически, он может иметь нулевую ценность для бизнеса. У теста есть стоимость: поддержка, время вычислений, администрирование и так далее. Значит, у теста может быть чистая отрицательная ценность. И это четвертая категория тестов, которые необходимо удалять. Такие тесты, не смотря на их способность что-то проверять, в действительности ничего не проверяют.

Если вы не можете объяснить, как сбой какого-либо юнит теста отражается на рисках, следует подумать о возможном удалении такого теста. При отсутствии формальных критериев корректности, существуют более эффективные техники устранения изъянов в качестве, такие как исследовательское тестирование и методы Монте-Карло. (Они великолепны, и я рассматриваю их как отдельную категорию в отличие от того, о чем рассказываю здесь.) Не используйте юнит тесты для подобных проверок.

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

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

1.6 Сложное - сложно

Существует следующая дилемма: большая часть интересных показателей о качестве определенных программ находиться в распределении результатов тестирования, несмотря на то, что традиционные подходы к статистике, всё же, предоставляют ложную информацию. Так, в 99,99% всех случаев тест может быть успешным, но однажды упав за десять тысяч раз, он убьет вас. Опять же, заимствуя аналогию из мира железа, для уменьшения вероятности ошибки до сколь угодно низкого уровня, вы можете всё проектировать с учетом заданной вероятности отказа или же провести анализ наихудшего случая (WCA). Специалисты по аппаратному обеспечению обычно используют WCA при проектировании асинхронных систем для защиты от сбоев в сигналах, выходящих за пределы проектных параметров: один сбой на 100 миллионов раз. В области аппаратного обеспечения, сказали бы, что коэффициент качества (FIT rate) такого модуля равняется 10 - десять отказов на триллион (Failures In a Trillion).

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

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

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

Большинство программистов убеждены, что построчное покрытие исходного кода, или, по крайней мере, покрытие ветвлений является вполне достаточным. Нет. С точки зрения теории вычислений, покрытие наихудшего случая означает анализ всевозможных комбинаций в последовательностях работы машинного языка, при котором гарантируется достижение каждой инструкции, а также - воспроизведение каждой возможной конфигурации битов данных в каждом из значений счетчика команд выполняемой программы. (Недостаточна и симуляция состояния среды выполнения только лишь для модуля или класса, содержащего тестируемую функцию или метод: как правило, любое изменение в каком-либо месте может проявиться в любом другом месте программы, а поэтому, потребует повторного тестирования всей программы. Формальное доказательство предложено в статье: Перри и Кайзера (Perry and Kaiser), Адекватное тестирование и объектно-ориентированное программирование (Adequate Testing and Objectoriented Programming), Журнал объектно-ориентированного программирования 2 (5), январь 1990 г., стр. 13). Даже взяв небольшую программу, мы уже попадаем в такое тестовое окружение, количество комбинаций в котором намного превышает количество молекул во Вселенной. (Мое определение понятия покрытие кода - это процент всех возможных пар, {Счетчик команд, Состояние системы}, воспроизводимых вашим набором тестов; все остальное - эвристика, которую, очевидно, вам сложно будет как-либо обосновать). Большинство выпускников бакалавриата смогут распознать проблему остановки (Halting Problem) в большинстве вариантов подобных задачах и поймут, что это невозможно.

1.7 Меньше - это больше или вы не шизофреник

Вот еще одна проблема, которая имеет особое отношение к первоначальному вопросу моего клиента. Наивный тестировщик пытается извлечь множество данных из результатов тестирования, при этом постоянно поддерживая все существующие тесты или даже добавляя новые; это приводит к точно такой же ситуации, в которой оказался мой клиент, когда сложность тестов (объемы кода или какие-только-хотите-метрики) начинает превосходить сложность исходного кода. Тестируемые классы - это код. Тесты - это код. Разработчики пишут код. Когда разработчики пишут код, они допускают около трех ошибок, непосредственно влияющих на систему, на каждые тысячу строк кода. Если бы мы случайным образом выбрали участки кода с подобными ошибками у моего клиента, включая тесты, то обнаружили бы, что в тестах содержиться код, который приводит к неправильным результатам чаще, чем реальный баг, останавливающий выполнение кода!

Некоторые мне говорят, что подобное не имеет к ним отношения, поскольку они уделяют значительно больше внимания тестам, чем исходному коду. Во-первых, это просто вздор. (Меня действительно смешат утверждающие, что, с одной стороны, они способны забывать о своих ранее сделанных предположениях во время создания изначального кода, и, с другой, те, кто может привнести свежий и независимый взгляд во время тестирования. Как первые, так и вторые должны быть шизофрениками.) Посмотрите, что делают разработчики при запуске тест-сьютов: они их запускают, но не думают (кстати, это же относится и к большей части Agile манифеста). На моей первой работе в Дании был проект, в значительной степени построенный на XP методологии и юнит тестировании. Я всячески пытался собрать билд на своей локальной машине, и после долгой борьбы с Maven и другими инструментами, наконец-то, мне это удалось. Каким же было разочарование, когда я обнаружил, что юнит-тесты не проходят. Пришлось обратиться к своим коллегам, которые сказали: О, так тебе нужно запустить Maven с вот этим флагом, он отключает вот эти тесты - из-за изменений эти тесты уже не работают, поэтому их необходимо отключить.

Если у вас 200, 2000, или 10 000 тестов, вы не будете тратить время на тщательное исследование и (кхе-кхе) рефакторинг каждого из них каждый раз, когда тест падает. Самая распространенная практика, которую я наблюдал, работая в стартапе еще в 2005 году, - это просто переписать результат старых тестов (ожидаемый результат или результаты вычислений такого теста) новыми результатами. С психологической перспективы, зеленый статус - это вознаграждение. Современные быстрые машины создают иллюзию возможности замены мышления программиста; их скорость намекает на исключение моей необходимости мыслить. Ведь, в любом же случае, если клиент сообщит об ошибке, я, в свою очередь, сформулирую гипотезу о ее действительной причине, внесу изменения, исправляющие поведение системы, и, в результате, с легкостью смогу себя убедить, что функция, в которую я добавил исправление, теперь работает правильно. То есть я просто переписываю результат выполнения этой функции. Однако, подобное - просто лженаука, основанная на колдовстве, связь с которым - причинность. В таком случае, необходимо повторно запустить все регрессионные и системные тесты.

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

1.8 Вы платите за поддержку тестов и качество!

Суть в том, что код - это часть вашей системной архитектуры. Тесты - это модули. Тот факт, что кто-то может не писать тесты, не освобождает его от ответственности заниматься проектированием и техническим обслуживанием возрастающего количества модулей. Одна из методик, которую часто путают с юнит-тестированием, но использующая последнее в качестве техники - это разработка через тестирование (TDD). Считается, что она улучшает метрики сцепления и связности (coupling and coherence), хотя, эмпирические данные свидетельствуют об обратном (одна из статей, опровергающих подобное представление на эмпирических основаниях принадлежит Янзену и Саледиану (Janzen and Saledian), Действительно ли разработка через тестирование улучшает качество проектирования программного обеспечения? IEEE Software 25(2), март/апрель 2008 г., стр. 77 - 84.) Еще хуже то, что таким образом, в качестве запланированного изменения, вы уже вводите связанность (coupling) между каждым модулем и сопровождающими их тестами. У вас появляется необходимость относиться к тестам так же как и к системным модулям. Даже если вы удаляете их перед релизом, это никак не сказывается на необходимости их обслуживать. (Подобное удаление может быть даже достаточно плохой идеей, но об этом дальше.)

Я обратил внимание, что подавляющая часть юнит-тестов, особенно созданных с помощью JUnit, представляет собой замаскированные утверждения (assertions). Когда же я создаю какое-нибудь восхитительное программное обеспечение, то засыпаю его подобными утверждениями, описывая ожидаемые мной результаты, и по отношению к тем, кто вызывает мои функции, и в отношении самих функций, которые обязаны что-то предоставить своим клиентам. Такие проверки дорабатываются в том же артефакте, где находится и остальная часть моего кода. Множество энвайерментов предоставляют средства административной стерилизации этих тестов во время релиза.

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

Превратите юнит-тесты в утверждения (assertions). Используйте их для обеспечения отказоустойчивости архитектуры высокодоступных систем. Это решает проблему поддержки множества дополнительных программных модулей, которые оценивают ход выполнения (программы) и проверяют правильность (её) поведения; это одна часть юнит-тестирования. Другая - это драйвер, выполняющий сам код: в этом отношении, рассчитывайте, на свои стресс-, интеграционные и системные тесты.

Почти последнее, существуют такие юнит-тесты, которые повторяют системные, интеграционные или другие виды тестов. На заре вычислений, когда компьютеры были медленными, вместо того, чтобы дожидаться запуска системных тестов, юнит-тесты предоставляли разработчику более быструю обратную связь о том, сломало ли их изменение код. Сегодня, когда появились более дешевые и мощные компьютеры, этот аргумент кажется менее убедительным. Каждый раз, внося изменения в свое приложение Scrum Knowsy, я тестирую его на системном уровне. Разработчики должны непрерывно интегрироваться и, так же непрерывно проводить тестирование системы, а не сосредотачиваться на своих юнит-тестах и откладывать интеграцию, даже на час. Так что избавляйтесь от юнит-тестов, которые дублируют то, что уже делают системные тесты. Если системный уровень обходится слишком дорого, создайте наборы интеграционных тестов. Рекс (Rex) считает, что следующим большим скачком в тестировании будет разработка таких юнит, интеграционных и системных тестов, которые устраняют случайные упущения и дублирование.

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

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

1.9 Это процесс, глупец или лихорадка зеленого статуса

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

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

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

Системные тесты почти сразу же погружают вас в подобную позицию размышления. Разумеется, вам все еще нужна будет более подробная информация, для этого на помощь приходит отладка (debugging). Отладка - это использование инструментов и устройств, помогающих изолировать ошибку. Отладка - не тестирование. Во-первых, она представляет собой ad-hoc (интуитивную активность) и, во-вторых, выполняется на основании последовательного перехода от ошибки к ошибке. Юнит тесты могут быть полезным инструментом отладки. На собственном опыте я обнаружил, что лучше всего работает комбинация различных инструментов, среди которых наиболее эффективными являются наборы с невалидными данными и доступ к глобальному контексту, включающий все значения данных и случайную трассировку стека.

1.10 Подводим итоги

Вернемся к моему клиенту из компании Sogeti. Вначале, я упоминал его высказывание:

Когда я ежедневно программировал, то создавал действительно тестируемый код, однако почти никогда не писал никакие юнит тесты. При этом я заслуживал признание за свой качественный код и почти безошибочное программное обеспечение. Мне хотелось бы разобраться, ПОЧЕМУ такой вариант работал в моем случае?

Возможно, Ричард - это один из тех редких людей, которые знают, как думать самому, вместо того, чтобы позволять компьютеру думать за него - будь то проектирование системы или дизайн более низкого уровня. Я чаще наблюдаю подобную ситуацию в Восточноевропейских странах, где отсутствие общедоступного компьютерного оборудования заставляло людей мыслить. Просто не хватало компьютеров. Когда я впервые посетил Сербию в 2004 году, студенты из ФОН (факультета информатики) могли получить доступ к компьютеру для выхода в интернет один раз в неделю. Расплата за ошибку высока: если запуск вашего кода не отработает, придется ждать еще неделю, чтобы повторить свою попытку.

К счастью, я вырос именно в такой культуре программирования, мой код записывался на перфокартах, которые отдавались оператору для установки в очередь машины, а затем, через сутки, собирались результаты. Такой формат действительно заставлял вас или же задуматься - или же, потерпеть неудачу. У Ричарда из Sogeti было аналогичное воспитание: у них была неделя на подготовку кода и всего один час на его запуск. Всё должно было делаться правильно с первого раза. В любом случае, обдуманный проект должен оценивать возможные риски, связанные с затратами, и устранять их по одному в каждой итерации, уделяя особое внимание постоянно растущей ценности. Одна из моих любимых циничных цитат: Я считаю, что недели программирования и тестирования могут сэкономить мне часы планирования. Что меня больше всего беспокоит в культуре раннего провала (fail-fast), так это не столько понятие провала, сколько слово раннее. Много лет назад мой босс Нил Халлер мне сказал, что отладка - это не то, что вы делаете, сидя перед своей программой с отладчиком; это то, что вы делаете, откинувшись на спинку стула и глядя в потолок, или обсуждение ошибки с командой. Однако многие, якобы ярые приверженцы эджайл методологий, ставят процессы и JUnit выше людей и взаимодействий.

Лучший пример, услышанный мной в прошлом году, был от моей коллеги, Нэнси Гитинджи (Nancy Githinji), управлявшей вместе со своим мужем IT-компанией в Кении; сейчас они оба работают в Microsoft. Последний раз, посещая свой дом (в прошлом году), она познакомилась с детьми, которые проживают в джунглях и пишут программы. Они могут приезжать раз в месяц в город, чтобы получить доступ к компьютеру и апробировать свой код. Я хочу нанять этих детей!

Мне, как стороннику эджайла (да и просто из принципа), немного больно признавать, что Рекс оказался прав, как, впрочем-то это было и ранее , достаточно красноречиво сказав: В этой культуре раннего провала (fail fast) есть нечто небрежное, она побуждает швырнуть кучу спагетти на стену, особо даже не задумываясь отчасти, из-за чрезмерной уверенности в заниженных рисках, предоставляемых юнит-тестами. Культура раннего провала может хорошо работать при очень высокой дисциплине, подкрепленной здоровым скептицизмом, однако редко можно встретить такое отношение в динамичном IT-бизнесе. Иногда ошибки требуют обдумывания, а последнее требует больше времени, чем результаты, достигаемые ранним провалом. Как только что напомнила моя жена Гертруда: Никто не хочет, чтобы ошибки затягивались на долго

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

Что касается Интернета: грустно и откровенно страшно, что там так много всего. Много советов, но очень мало из них подкреплено теорией, данными или даже моделью того, почему необходимо довериться тому или иному совету. Хорошее тестирование немыслимо без скептицизма. Относитесь скептически к себе: измеряйте, доказывайте, делайте повторные попытки. Ради всего святого, отнесись ко мне скептически.

Пишите мне свои комментарии на jcoplien@gmail.com с копией Рексу вначале этого письма.

В заключение:

  • Сохраняйте регрессионные тесты до года, большинство из них должны быть тестами системного уровня, а не юнит-тестами.

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

  • Исключая предыдущее заявление, если у X есть определенная бизнес-ценность и вы можете протестировать X системным или же юнит-тестом, используйте системный: контекст - это всё.

  • Разрабатывайте тест более тщательно, чем код.

  • Превратите большинство юнит-тестов в утверждения (assertions).

  • Удалите тесты, которые за год ни разу не падали.

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

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

  • Будьте скромны в отношении способностей тестов. Тесты не улучшают качество: это делают разработчики.

Подробнее..

RPA инструменты и не только

06.06.2021 16:09:40 | Автор: admin

Однажды на работе мне поставили R&D задачу создать бота, который будет "ходить" по сайту, выбирать товары, заполнять формы и оплачивать покупки. На тот момент мы писали часть Antifraud системы, которая позволяла детектировать ботов в браузере. И с этого момента все началось...

Оглавление

  1. Коротко о RPA

  2. Open source проекты

  3. Платные сервисы

  4. Test Automation

  5. RPA vs Test Automation

  6. Парсинг сайтов и RPA

  7. BPM и RPA

  8. Безопасный RPA...

  9. Пример работы бота на Python

  10. Как детектировать бота?

  11. Выводы

Коротко о RPA

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

Более четкое определение:

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

За время создания своего бота я нашел несколько направлений RPA:

Направления в RPAНаправления в RPA

Open source проекты

Начнем сразу с open source проектов, т.к. они представляют большой интерес.
Рассмотрим инструменты относительно языков программирования:

RPA open sourceRPA open source

Конечно это не все инструменты, но по крайней мере основные, которые мне удалось найти. Я Python разработчик, поэтому рассмотрю только те инструменты, которые попробовал на практике.

Selenium & rpaframework

Объединил 2 технологии в 1 короткий обзор т.к. использовал их для одной и той же задачи: создание бота, который выбирает товары, добавляет их в корзину и оплачивает покупки. Цель: сдетектировать и заблокировать бота, используя fingerprint и треки мыши. О том как детектировать ботов будет в разделе "Безопасный RPA...".

Selenium

SeleniumWebDriver это инструмент для автоматизации действий веб-браузера. В большинстве случаев используется для тестирования Web-приложений, но этим не ограничивается. Очень часто с помощью данного инструмента создаются различные боты.
Selenium IDE - инструмент для создания сценариев быстрого воспроизведения ошибок; расширение Chrome и Firefox, которая будет выполнять простую запись и воспроизведение взаимодействий с браузером.

RPA Framework

RPA Framework - это набор библиотек и инструментов с открытым исходным кодом для RPA, предназначенный для использования с Robot Framework и с Python. Имеет синхронизацию с Selenium и Playwright, библиотека для автоматизации Chromium, Firefox и WebKit с помощью единого API. Входит в набор инструментов Robocorp для автоматизации с открытым исходным кодом.

3 in 1 (Desktop / Web / Mobile)

Robocorp

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

TagUI

TagUI - бесплатный инструмент RPA от AI Singapore, финансируемой программой по ускорению развития ИИ. Проект TagUI является открытым и бесплатным. Его легко настроить и использовать, он работает в Windows, macOS и Linux.

TagUI RPATagUI RPA

Из всех инструментов мне больше всего понравился RPA Framework, у которого есть возможность работать с Playwright, также в этом фреймворке очень удобные selector в отличие от Selenium, что позволяет гораздо быстрее писать код.

Пример на Selenium и на RPA Framework

Selenium

from selenium import webdriverfrom selenium.webdriver.common.keys import Keysfrom webdriver_manager.chrome import ChromeDriverManagerdriver = webdriver.Chrome(executable_path=ChromeDriverManager().install())driver.get("https://www.google.com/")elem = driver.find_element_by_xpath("/html/body/div[1]/div[3]/form/div[1]/div[1]/div[1]/div/div[2]/input")elem.send_keys("Python news")elem.send_keys(Keys.RETURN)driver.close()

RPA Framework

from RPA.Browser.Playwright import Playwrightfrom Browser.utils.data_types import KeyActionlib = Playwright()lib.open_browser("https://www.google.com/")lib.fill_text(selector="input", txt="Python news")lib.keyboard_key(KeyAction.press, "Enter")lib.close_browser()

На мой взгляд у RPA Framework более удобное API.

Платные сервисы

Сказать что их много значит ничего не сказать. Платных Enterprise версий огромное количество. Но проблема в том, что многие организации не понимают истинной разницы в возможностях одного программного обеспечения RPA и другого. Возможно, они также слышали, что RPA предназначена только для очень крупных корпораций и что минимальные первоначальные инвестиции в лицензирование могут достигать 100 000 долларов в год, что теперь не всегда так.

RPA productsRPA products

Список ведущих поставщиков RPA на основе матрицы пиковых значений Everest Group для поставщиков технологий RPA 2020:

Everest группирует инструменты RPA в три основных сегмента в зависимости от их возможностей, влияния на рынок и способности успешно поставлять продукт. Everest также выделяет UiPath, Automation Anywhere, Blue Prism, Intellibot и Nividous в качестве лидеров.

UiPath vs Automation Anywhere vs Blue Prism

Компания Blue Prism, основанная в 2001 году, была пионером в секторе RPA и использовала термин Robotic Process Automation. Четыре года спустя генеральный директор UiPath Дэниел Дайнс технически основал UiPath как компанию под названием DeskOver. Однако только в 2015 году она действительно родилась и была переименована в RPA-компанию.

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

VSVS

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

Спустя годы на рынок вышли более доступные конкуренты, такие как UiPath и Automation Anywhere с такими же (или лучшими) возможностями, повышенной гибкостью и простотой внедрения. В результате RPA была демократизирована для компаний любого размера во многих отраслях, включая малые и средние организации.

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

  1. UiPath - 4.6 / 5 звезд с 4722 отзывами

  2. Automation Anywhere оценивает 4,5 / 5 звезд с 4310 отзывами

  3. Blue Prism 4,4 / 5 звезд по 158 отзывам

Что делает UiPath самой популярной платформой RPA?

UiPath превратился в единственную платформу RPA на рынке, созданную для поддержки полного жизненного цикла автоматизации. Портфель продуктов компании продолжает оставаться в авангарде инноваций, постоянно расширяя свои традиционные возможности RPA за счет включения таких инструментов, как интеллектуальный анализ процессов, встроенная аналитика, улучшенные компоненты AI Fabric, RPA на основе SaaS и автоматизация тестирования.

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

Другие ключевые сильные стороны UiPath:

  1. Long Running Workflows

  2. Machine Learning and Predictive Analytics

  3. Seamless Interconnectivity

  4. Process Document Understanding

  5. Citizen Development

  6. Customer Satisfaction

  7. Flexible Licensing Model and Low Cost of Entry

Оригинал статьи со сравнением: https://www.auxis.com/blog/top-rpa-tools

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

G2 GridG2 GridМини обзор популярных и не очень RPA

UiPath

Самое замечательное в UiPath - это простота использования.

UiPath прост в установке и имеет возможности разработки на основе пользовательского интерфейса. Подробное онлайн-руководство поможет быстро освоиться. Согласно Quadrant Review компании Gartner, UiPath имеет первоклассную команду поддержки клиентов, и в целом UiPath идеально подходит для компаний, стремящихся к быстрому внедрению RPA.

GUI UiPathGUI UiPath

Automation Anywhere

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

GUI Automation Anywhere GUI Automation Anywhere

Blue Prism

Blue Prism, старейший инструмент в индустрии RPA, в последние годы неуклонно растет.

Blue Prism специализируется на сквозной RPA для компаний из списка Fortune. Blue Prism также предлагает высококлассных роботов. Роботы не только очень сложные, но и обладают глубокими возможностями создания сценариев для настройки расширенных сетей RPA. Имеет отличные возможности отладки и потрясающую масштабируемость.

GUI Blue PrismGUI Blue Prism

Microsoft Power Automate

Microsoft Power Automate предоставляет простое и эффективное решение RPA. Самым значительным преимуществом Microsoft Power Automate является простота настройки. Данные из экосистемы Microsoft легко доступны. Легко управлять оркестрацией робота.

WinActor

WinActor - это инструмент RPA, разработанный NTT Group. Он широко используется в таких отраслях, как разработка программного обеспечения и финансы.

GUI WinActorGUI WinActor

Test Automation

Автоматизация тестирования помогает разработчикам и тестировщикам экономить время и увеличивать охват тестированием за счет автоматизации повторяющихся тестовых случаев.

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

Automation Testing Tools

Инструментов ни сколько не меньше чем у RPA.

Вот небольшой список:

Инструмент

Open source

Платная

Selenium

+

Appium

+

SoapUI

+

TestProject

+

Cerberus Testing

+

Katalon Studio

+

IBM Rational Functional Tester

+

Telerik Test Studio

+

TestComplete

+

Ranorex

+

Kobiton

+

Subject7

+

HPE Unified Functional Testing (UFT)

+

Сводная картинка по некоторым инструментам:

QA Automation toolsQA Automation tools

RPA vs Test Automation

Коротко: это практически одно и то же.

RPA и Test Automation можно рассматривать как одно и то же. Компании используют их для автоматизации задач, экономии затрат и освобождения времени сотрудников для других дел.

Сходства:

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

Различия:

  • сценарии тестирования, созданные для автоматизации тестирования, зависят от тестируемой системы (SUT).

  • RPA не различают, автоматизируют ли они перенос данных в систему бухгалтерского учета или, например, помечают ваши электронные письма.

  • RPA инструменты не зависят от программного обеспечения, в котором запущен процесс.

Парсинг сайтов и RPA

Цели у компаний, которые занимаются парсингом сайтов, разные, но тем не менее такие инструменты есть и некоторые из них являются полноценным RPA инструментом (например, Octoparse).

Process Bots VS Search Bots

Process Bots VS Search BotsProcess Bots VS Search Bots

Сильные стороны RPA:

  • Low Code UX

  • Управление входами и выходами через UX

  • Работа с авторизацией для бизнес-приложений

  • Передача данных в бизнес-процессе

  • Бизнес-шаблоны для определенных шаблонов использования (обслуживание клиентов, финансовые таблицы и т.д.)

Сильные стороны поискового робота:

  • Масштабирование для одновременной обработки десятков тысяч страниц

  • Отсутствие конфигурации и автоматическая обработка для множества типов веб-страниц

  • Поисковые роботы автоматически адаптируются при изменении страниц

  • Богатая индивидуальная конфигурация

  • Всестороннее чтение HTML страницы (Имя автора; UPC продукта)

  • Автоматическое извлечение настроения из текста

  • Извлечение связанных сущностей из текста (организации, местоположения, люди и т.д.)

Но какие боты лучше?

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

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

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

Теперь возьмем поискового бота с поддержкой AI. Вводим один сайт например, в Crawlbot Diffbot, ждем несколько минут, и тысячи страниц распознаются и анализируются как страницы продуктов. Загружаем данные в формате JSON или CSV, либо загружаем приложение или панель инструментов с выбранными результатами. Основная технология, лежащая в основе этого варианта использования, возможно будет чем боты RPA. Поисковые боты сами ускоряют чтение и классификацию Интернета!

Инструменты для парсинга

Scrape.do

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

Scrapingdog

Scrapingdog - это инструмент для очистки веб-страниц, который упрощает работу с прокси-серверами, браузерами, а также с CAPTCHA. Этот инструмент предоставляет HTML-данные любой веб-страницы за один вызов API. Одна из лучших особенностей Scraping dog - это наличие API LinkedIn.

ParseHub

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

Diffbot

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

Octoparse

Octoparse выделяется как простой в использовании инструмент для парсинга веб-страниц без кода. Он предоставляет облачные сервисы для хранения извлеченных данных и ротации IP-адресов для предотвращения блокировки IP-адресов. Вы можете запланировать парсинг в любое определенное время. Кроме того, он предлагает функцию бесконечной прокрутки. Результаты загрузки могут быть в форматах CSV, Excel или API.

ScrapingBee

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

Luminati

Luminati - это веб-парсер с открытым исходным кодом для извлечения данных. Это сборщик данных, обеспечивающий автоматизированный и настраиваемый поток данных.

Scraper API

Scraper API - это прокси API для парсинга веб-страниц. Этот инструмент помогает управлять прокси-серверами, браузерами и CAPTCHA, поэтому вы можете получить HTML-код с любой веб-страницы, выполнив вызов API.

Scrapy

Еще один в нашем списке лучших инструментов для парсинга - Scrapy. Scrapy - это платформа для совместной работы с открытым исходным кодом, предназначенная для извлечения данных с веб-сайтов. Это библиотека для парсинга веб-страниц для разработчиков Python.

Import.io

Инструмент для парсинга веб-сайтов с оперативным управлением всеми веб-данными, обеспечивая точность, полноту и надежность. Import.io предлагает конструктор для формирования собственных наборов данных путем импорта данных с определенной веб-страницы и последующего экспорта извлеченных данных в CSV. Кроме того, он позволяет создавать более 1000 API-интерфейсов в соответствии с вашими требованиями. Есть приложение для Mac OS X, Linus и Windows.

BPM и RPA

Основная функция BPM-системы это организация правильного взаимодействия участников бизнес-процесса.

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

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

CAMUNDACAMUNDAСервисы BPM с интеграцией RPA

Camunda

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

Основные преимущества:

  • Проектирование сквозного процесса

  • Согласование сценариев RPA

  • Оперативноенаблюдение задействиями ботов RPA

  • Аналитика RPA

ELMA

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

Выгоды длябизнеса отиспользования RPA + BPM:

  • Снижение издержек нарутинные операции.

  • Масштабирование бизнеса безрасширения штата.

  • Освобождение времени сотрудников наболее интеллектуальный труд.

  • Лучший Customer Experience засчет качества искорости сервиса.

ProcessMaker

ProcessMaker - это простое в использовании программное решение для управления бизнес-процессами (BPM) и рабочими процессами. Сочетает корпоративную разработку с низким уровнем кода и ведущую в отрасли интеллектуальную автоматизацию рабочих процессов.

Безопасный RPA...

Взлом RPA

Можно ли взломать RPA? Да, можно. Например, обработка данных с сайта (загрузка картинок, текста и пр.), откуда робот может скачать зараженный скрипт под видом обычной картинки, а скаченный скрипт может повлиять на работу бота, добавляя новые правила в обработку, или просто остановит его. Много что можно сделать, выбор огромный.

Риски безопасности, на которые стоит обратить внимание:

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

  • Операционные риски или риски исполнения: развертывание правильной операционной модели необходимо для уменьшения функциональных проблем или проблем с производительностью.

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

  • Раскрытие конфиденциальных данных:malware проникает в систему и создает сценарий при котором данные пользователей утекают в сеть.

  • Отказ в обслуживании: создания необходимых условия для остановки работы бота.

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

Проблема безопасности данных может быть разбита на два тесно взаимосвязанных момента:

  • Безопасность данных

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

RPA для пентеста

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

Продолжение следует...

В следующей статье мы создадим своего бота, рассмотрим какие есть алгоритмы детектирования ботов, которые написаны на Selenium и с помощью RPA. Создадим свой алгоритм, подведем итоги.

Подробнее..

Impact Analysis 6 шагов, которые облегчат тестирование изменений

25.01.2021 22:13:40 | Автор: admin
Содержание
  • Что такое Impact Analysis?

  • Когда нужно проводить Impact Analysis?

  • Для чего нужно проводит Impact Analysis?

  • Как провожу Impact Analysis я?

    • 1. Изучение issue\ticket\bug\change request *

    • 2. Чтение emails **

    • 3. Разговор с разработчиками **

    • 4. Изучение места, где было сделано изменение ***

    • 5. Изучение описания изменений ***

    • 6. Исследование кода изменений *****

  • Почему я решила написать об этом?

Что такое Impact Analysis?

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

Затронутые области требуют большего внимания во время проведения регрессионного тестирования.

Отмечу сразу, чтобы не пугать QA: импакт анализ не есть "чтение кода". Он включает в себя и иные способы исследования.

Когда нужно проводить Impact Analysis?

Импакт анализ может быть полезным в следующих случаях:

  • есть изменения в требованиях;

  • получен запрос на внесение изменений в продукт;

  • ожидается внедрение нового модуля или функциональности в существующий продукт;

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

Как мы знаем, в настоящее время продукты становятся всё более большими и комплексными, а компоненты всё чаще зависят друг от друга. Изменение строчки кода в таком проекте может "сломать" абсолютно всё.

Developers are fixing Production IssueDevelopers are fixing Production Issue

Для чего нужно проводит Impact Analysis?

Информация о взаимосвязи и взаимном влиянии изменений могут помочь QA:

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

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

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

Как провожу Impact Analysis я?

  1. Изучаю issue\ticket\bug\change request *.

  2. Читаю email переписку **.

  3. Разговариваю в разработчиками **.

  4. Смотрю на место где было изменение (commit place) ***.

  5. Смотрю на описание изменения (commit description) ***.

  6. Смотрю на изменения в коде *****.

'*' показывает "уровень сложности" этого действия. Как видно, только "шаг 6" требует умения читать код, с "шагами 1-5" способен справится QA и без знаний языком программирования.

1. Изучение issue\ticket\bug\change request *

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

  • Steps To Repeat;

  • Description;

  • Additional Background Information;

  • Attachment;

Некоторые важные условия или особенности, описанные в ишью, помогут вам идентифицировать область тестирования. Например, при внимательном прочтении ишью, вы обнаружили, что в 'Additional Background Information' стоит пометка, что проблема воспроизводится только при использовании HTTPs. Поэтому для себя можно отметить, что при тестировании неплохо было бы проверить и случай с HTTP.

2. Чтение emails

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

  • больше деталей от заказчиков;

  • результаты исследований от других членов команды;

  • список похожих проблем;

  • картинки, графики, схемы и другое.

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

Why wasn't it mentioned in the issue?!Why wasn't it mentioned in the issue?!

3. Разговор с разработчиками **

QAs and Developers QAs and Developers

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

4. Изучение места, где было сделано изменение ***

Изменения, которые сделаны, должны быть куда-то внесены. В моём случае, это git, где достаточно легко можно определить место, где были сделаны изменения. Под "местом" я понимаю "конкретный файл, конкретная функция\метод, конкретный модуль". И следовательно, это "место" (этот модуль, функциональность) следует перепроверить, протестировать на наличие регрессий.

File where changes areFile where changes are

Например, на картинке выше видно, что изменения были в 'ExtendedClassification' функциональности, поэтому хотя бы, Smoke Test для этой функциональности должен быть пройден.

Так же, если изменения были в каких-то клиентских файлах ( JS, HTML, CSS, etc.), то следует провести кроссбраузерное тестирование.

Сложность "***" - чтение кода всё ещё не требуется, но нужно иметь представление об архитектуре проекта.

5. Изучение описания изменений ***

Для того, чтобы QA могли описания изучать, сначала нужно добиться того, чтобы Developers начали писать грамотные и понятные описания изменений. В моём отделе мы придерживается следующего шаблона, для описания изменении (git commits):

Ticket number and title

- Bug:

{В чём состоит дефект, какое актуальное поведение системы?}

- Problem:

{первопричина дефекта, что в системе работает не так?}

- Fix:

{в чём состоит изменение}

Changes descriptionChanges description

Например, исходя из описания исходной проблемы "Логика не работает при версировании root ItemType" (изображение сверху), следует, что нужно проверить "данную логику при версировании root ItemType". И имея в наличие только bug и его описание, эта важная проверка не столь очевидна и может быть пропущена.

6. Исследование кода изменений *****

Тут всё банально просто - нужно читать код и представлять, что он делает. Конечно, не многим это под силу на данном этапе, но есть к чему стремиться. К тому же, он QA не требуется глубокого понимание кода. Базовых знаний программирования и поверхностных знаний ООП (в моём случае) вполне достаточно, чтобы представить use case, покрывающий основные функции этого кода.


Почему я решила написать об этом?

Недавно в отделе столкнулась с тем, что разработчик сделал кое-какие изменения и отправил это в тестирование QA. Тестировщик естественно принял решение провести регрессионное тестирование, но без особого разбора и исследований установил, что нужно пойти все регрессионные тесты, которые в сумме занимают 25ч (проверка доступов, отработка разной функциональности, проверка в разных браузерах). Когда я решила взглянуть на сделанные изменения, то обнаружила следующее:

ExampleExample

т.е. единственное изменение, которое было сделано, - это добавлена проверка, если ItemType не равен "HG_Modification Orger", то делай всё то же, что и делал раньше (сделай изменения и обнови окно), если ItemType равен "HG_Modification Orger", то пропусти (ничего не делай и просто обнови окно). Т.е. эти изменения никак не относятся к клиентской части (проверка в разных браузерах не нужна). Изменения никак не затрагивают доступы, отрисовку. Они затрагивают лишь определённую функциональность, которую нужно проверить для ItemType = "HG_Modification Orger" и для ItenType != "HG_Modification Orger". Эти проверки займут 30 минут в худшем случае. Но никак не 25 часов.

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

Подробнее..

Какие вопросы ожидать на позицию автоматизатора и причем тут сортировка?

03.04.2021 16:09:23 | Автор: admin

Здравствуйте, коллеги.

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

Само собой, если вы проходите собеседование на позицию junior, от вас не будут требовать опыта и знаний по всем вопросам. Будет круто, если вы разбираетесь хотя бы в ~30% всего этого. От позиции middle я бы ожидал примерно ~50%-60% знаний перечисленных мною тем. Ну и далее по восходящей. :)

Кто такой автоматизатор?

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

Я всегда люблю говорить, что автоматизация тестирования - это три направления одновременно. Чтобы заниматься этим эффективно необходимо:

  1. Разбираться в тестировании. Иначе не получится писать хорошие тесты, проверяющие именно то, что необходимо.

  2. Уметь программировать. Конечно, на первый взгляд тесты кажутся очень простыми. Последовательно вызываешь методы кликов, ввода значения и так далее. Но это неплохо работает только на небольшом количестве тестов, да и то если сам продукт меняется не слишком часто и работает стабильно. Чем больше тестов и сложнее продукт, тем больше опыта в программировании потребуется от автоматизатора для эффективной работы.

  3. Знать, как развернуть необходимый софт на серверах. Читай - немного понимать в системном администрировании или девопсе. Ведь тесты чаще всего запускаются в CI-системе, привязываются к Pull Requestам, а сам код запускается в Docker. И со всем этим надо уметь работать.

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

Тестирование

Обычные вопросы по тестированию чаще всего затрагивают теорию и практику.

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

Скорее всего спросят про знание устройства самого продукты, который вы будете тестировать. Например, если вам предстоит работать с Web, надо понимать как он работает: разбираться в протоколе HTTP, знать о связке HTML / CSS / JavaScript, понимать смысл кросс-браузерного тестирования и так далее.

Для тестирования мобильных приложений надо понимать основное отличие операционных систем iOS и Android, особенности тестирования на симуляторах, эмуляторах и реальных девайсах и многое другое.

Само собой, поскольку все это нам предстоит автоматизировать, еще необходимо разбираться в стеках автоматизации. Для автоматизации тестирования Web необходимо понимать как настроить Selenium или Selenoid, как подбирать CSS или XPath-локаторы для элементов, какие браузеры выбрать для тестов.

Для мобильной автоматизации пригодится знание драйверов (Espresso или XCUITest) или опыт работы с Appium. Умение настраивать ферму девайсов или устанавливать необходимые эмуляторы и симуляторы.

Для автоматизации API необходимо знать про методы HTTP-запросов (GET, POST, PUT, DELETE и т.д.) и их отличия, коды ответа сервера и их основные форматы (JSON, XML).

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

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

Программирование

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

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

Чаще всего причина недовольства простая - мне это не нужно для работы. И в целом это правда. Не каждый день вы будете реализовывать методы сортировки самостоятельно. Тем более для написание тестов. Тогда зачем это спрашивают?

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

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

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

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

Чаще всего человек, который действительно пишет много кода, справится с подобными задачами. Особенно если немного попрактикуется решать именно такие специфичные задачи. Для этого существуют такие ресурсы, как leetcode, codewars и прочие.

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

Еще на собеседовании могут поспрашивать немного про паттерны программирования. Тут хорошо знать про Singleton, Factory, PageObject, PageFactory, Builder и так далее. Можно еще почитать про принципы разработки SOLID, KISS, DRY, SRP.

Девопс

Ну и заключительная часть - это работа с различным софтом и инструментами. Тут могут спросить с какой CI-системой вы чаще всего работали. На мой взгляд, самыми популярными являются Jenkins, Gitlab CI, TeamCity и Bamboo.

Помимо этого спросят про опыт работы с bash: команды cd, ls, ps, mv, cp и так далее. Просто, чтобы убедиться, что вы не растеряетесь, зайдя на какой-нибудь сервер на основе linux по ssh.

Еще могут быть вопросы по Docker - что такое образ, как запустить контейнер, как сделать маунт директории хост-машины, как собрать docker compose файл, как распространят образы между коллегами (docker registry)... Примерно так.

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

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

Итог

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

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

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

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

Я думаю, про это я напишу как-нибудь в следующий раз.

Ну и минутка рекламы - приходите на наши курсы для тестировщиков. На них мы рассказываем многое как из этого списка, так и в целом о тестировании. Вся информация и ссылки в профиле. :)

А на этом все. Спасибо за внимание.

Подробнее..

Перевод Лучшие практики автоматизации тестирования решение, что и когда автоматизировать

18.05.2021 20:09:49 | Автор: admin

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

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

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

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

Автоматизируйте ваши смоук тесты

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

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

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

  3. Приводит к меньшему количеству ручного труда и экономит время. Путем интеграции ваших автотестов в пайплайн CI/CD ваши смоук тесты сворершают проверку до завершения сборки. Это означает, что сборка не передается QA, если автотесты не пройдут смоук.

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

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

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

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

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

Автоматизируйте обширные тесты

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

Автоматизируйте тесты, требующие нескольких конфигураций

Речь идет о тестах в различных операционных системахи комбинациях браузеров. Делать все это вручную - утомительно. Также, автоматизация таких тестов может помочь сэкономить время. Автотесты можно запускать в различных средах (таких как Dev, QA, Staging, Integration или PROD), просто изменив переменную среды. Тесты также можно запускать параллельно, что сокращает время, необходимое для выполнения. Вы можете использовать различные инструменты CI, такие как CircleCI, чтобы указать ОС, браузеры и среды, в которых вы хотите запускать параллельные тесты. Убедитесь, что вы следуете лучшим практикам при создании параллельных тестов, чтобы получить от них максимальную пользу.

Автоматизируйте ваши тесты производительности

Это позволяет избежать сбоев при запуске, и снижения производительности. Тестирование производительности проверяет, как система работает под нагрузкой и стрессом, а также выявляет узкие места. Он проверяет скорость, время отклика, надежность, использование ресурсов и масштабируемость программного обеспечения в соответствии с ожидаемой рабочей нагрузкой. Автоматизация может помочь вам легко сгенерировать тысячи пользователей, чтобы увидеть, как приложение отреагирует. Например, Fandango - один из крупнейших в Америке сайтов по продаже билетов (около 36 миллионов посетителей в месяц покупают билеты на их сайте), и они хотели убедиться, что готовы к фильму Звездные войны: Последний джедай. Они хотели узнать, какова их пиковая производительность и определить узкие места. QualityWorks автоматизировала тесты производительности и предоставила им отчеты, которые помогли бы выявить узкие места, и в результате они успешно запустили продажу фильмов. Это то, что они могут продолжать использовать, чтобы гарантировать, что производительность их веб-сайта соответствует определенным стандартам.

Ваш следующий шаг

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

Переведено командой QApedia. Еще больше переведенных статей вы найдете на нашем телеграм-канале.

Подробнее..

Перевод Cypress VC Selenium

17.06.2021 18:06:38 | Автор: admin

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

Вот вам вопрос на миллион долларов: является ли Cypress чем-то большим, чем платформа для автоматизации веб-тестов и может ли он заменить Selenium?

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

Краткое описание

Cypress - это тестовая веб-платформа нового поколения. Она была разработана на основе Mocha и Chai и представляет собой платформу для сквозного тестирования на основе JavaScript.

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

Архитектура

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

В Selenium, когда мы запускаем сценарий автоматизации Selenium, клиентская библиотека Selenium взаимодействует с Selenium API, который отправляет команду привязки драйверу браузера с помощью проводного протокола JSON. Драйвер браузера использует реестр HTTP для фильтрации всех команд управления HTTP-запроса и HTTP-сервера. Затем команды браузера выполняются в скрипте selenium, а HTTP-сервер отвечает скрипту автоматизированного тестирования.

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

Установка

В Cypress нет никакой предварительной настройки, просто установите файл.exe и автоматически настройте все драйверы и зависимости. Автоматизация может быть выполнена за считанные минуты. Одной из философий дизайна Cypress было сделать весь процесс тестирования удобным и комфортным для разработчиков, упаковки и объединения.

Чтобы использовать Selenium в проекте автоматизации, необходимо установить библиотеки линковки языков для использования предпочитаемого языка. Кроме того, в отношении браузеров, для которых вы хотите автоматически проводить проверки, вам понадобятся двоичные файлы для WebDriver.

Если мы также примем во внимание время и сложность имплементации, здесь Cypress имеет преимущество над Selenium.

Поддерживаемые языки

Cypress поддерживает только JavaScript. Поддержка других языков отсутствует, что иногда вынуждает пользователя изучать определенный язык сценариев.

Selenium, в свою очередь, поддерживает широкий спектр языков Java, C#, Python, Ruby, R, Dar, Objective-C, Haskell и PHP, а также JavaScript.

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

Кросс-браузерная поддержка

Cypress поддерживает Canary, Chrome, Chromium, Edge, Edge Beta, Edge Canary, Edge Dev, Electron, Firefox (бета-поддержка), Firefox Developer Edition (бета-поддержка), Firefox Nightly (бета-поддержка).

Selenium поддерживает почти все основные браузеры на рынке, что является дополнительным преимуществом Selenium. Ниже приведен список поддерживаемых браузеров: Chrome (все версии), Firefox (54 и новее), Internet Explorer (6 и новее), Opera (10.5 и новее), Safari (10 и новее).

Selenium имеет более качественную кросс-браузерную поддержку по сравнению с Cypress, потому что Selenium обеспечивает поддержку почти всех доступных на рынке браузеров, в то время как в Cypress вы не можете проводить тестирования на Safari.

Параллельное исполнение пакета автоматизированного тестирования

По сравнению с Selenium в параллельной проверке, cypress отстает.

Selenium имеет много вариантов для параллельного исполнения, что для автоматизации тестирования очень важно. Grid широко используется в сообществе QA с TestNG для параллельного исполнения. А контейнеризация Docker может быть быстро интегрирована.

Производительность

Так как у Cypress нет нескольких архитектурных слоев, в отличие, например, от Selenium, он работает в браузере аналогичным образом. Именно поэтому мы наблюдаем его значительное преимущество над Selenium в скорости выполнения тестов.

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

Интеграция процессов автоматизации с CI/CD

Cypress: Возможна, но ограничена. Существует только одна альтернатива для командной строки и библиотеки npm - Mocha. Служба CI должна поддерживать npm, а запись тестов для большинства записей на сервере CI будет платной.

Selenium: Возможно применение CI/CD. Любая библиотека тестов, их запись и шаблоны исполнения могут использоваться и быстро адаптироваться к требованиям.

Лицензирование

Cypress также доступен под лицензией MIT как open-source. Однако, если сравнивать его с Selenium, все функции Cypress не будут бесплатными, например, приборная панель Cypress бесплатна для seed и платна для Sprout, Tree и Forest. (https://www.cypress.io)

Selenium разрешен под лицензией Apache 2.0, правообладатель Software Freedom Conservation.

Поддержка ОС

Cypress: Windows, Mac, Linux

Selenium: Windows, Linux, Mac, Android, iOS

Поддержка BDD и DataDrivenTesting

Selenium поддерживает BDD и data-driven с помощью внешних библиотек, что в Cypress невозможно.

Локаторы для идентификации объектов

Cypress поддерживает только CSS и Xpath.

Cypress поддерживает все виды веб-локаторов, такие как ID, Name, XPath, селекторы CSS, текстовые ссылки, текстовые частичные ссылки и т.д.

Отчет о выполнении

Selenium: Extent, Allure и любые другие дашборды могут быть реализованы в наборах автоматизации.

Cypress: Дашборд - это как раз Cypress.

Окончательный вердикт

Selenium больше ориентирован на специалистов по автоматизации тестирования, а Cypress - на разработчиков для повышения эффективности TDD. Selenium был представлен в 2004 году, поэтому у него больше поддержки экосистемы, чем у Cypress, который был разработан в 2015 году и продолжает расширяться. Когда мы используем Selenium, в браузере можно манипулировать несколькими различными опциями, такими как Cookies, Local Save, Screen, Sizes, Extensions, Cypress control line options, но опция сети может быть обработана только при использовании Cypress.

Однако, вот некоторые из преимуществ, о которых заявляет Cypress:

1. Программа будет делать скриншоты во время проведения экспериментов. Затем мы можем просмотреть каждый запрос в качестве разработчика теста на панели Test Runner, чтобы увидеть, что произошло на каждом этапе.

2. Для тестирования с помощью Cypress не требуется никакого ожидания или режима сна. Прежде чем продолжить, он немедленно ожидает команд и утверждений.

3. Подобно модульным тестовым ситуациям, "шпионы" Cypress могут подтверждать и проверять действия функций, ответы сервера или таймеры по времени. С помощью Cypress вы можете заглушить сетевой трафик и персонализировать вызовы API в соответствии с вашими потребностями.

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


В преддверии старта курса "Java QA Engineer. Professional" приглашаем всех желающих на бесплатный двухдневный интенсив, в рамках которого мы рассмотрим CI- и CD- процессы, изучим основные инструменты и ключевые понятия (Server, agents, jobs. Fail fast, Scheduling, WebHooks). Подробно познакомимся с программной системой Jenkins и научимся интегрировать ее с git и Docker.

Записаться на интенсив:

Подробнее..

5 причин, которые заставят тебя использовать Kibana

19.06.2021 02:15:45 | Автор: admin

Во многих компаниях для сбора и анализа логов используют такой инструмент как Kibana. Но существует проблема которая выражается в том, что этим инструментом редко или почти не пользуются. Почему же так происходит? Дело в том, что человек привык анализировать логи непосредственно на инстансе. Читать логи так сказать из первоисточника. Безусловно это самый лучший способ. И мало кто не любит менять свои привычки. Потому что это своего рода выход из зоны комфорта и к этому не все и не всегда готовы.

Кейсы чтения логов

Но бывают ситуации, когда нет возможности зайти непосредственно на инстанс. Например необходимо проанализировать инцидент случившийся на продакшене и у нас нет доступа к этому окружению по известным всем причинам. Другая ситуация, если сервис работает на операционной системе Windows, а доступ нужен для трех и более сотрудников одновременно. Как мы все хорошо знаем у компании Windows есть политика одновременной работы не более двух человек при входе по RDP (Remote Desktop Protocol). Для того чтобы получить одновременный доступ по RDP большему количеству сотрудников, необходимо купить лицензию, а на это готова пойти далеко не каждая компания.

Стек ELK

Таким образом мы возвращаемся к нашему замечательному инструменту Kibana. Kibana это часть стека ELK, в который помимо неё входят Elasticsearch и Logstash. Kibana используется не только для визуализации данных в различных форматах, но также и для быстрого поиска и анализа логов. И сегодня речь пойдет о том, как комфортно перейти на этот инструмент и какие у него есть скрытые возможности для этого.

Способы и лайфхаки

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

Так же можно составлять сложные поисковые запросы. Существует специальный язык запросов, называемый KQL (Kibana Query Language). С помощью этого языка можно составлять многоуровневые запросы, которые помогают отфильтровывать нужную информацию. Например можно выбрать тестовое окружение и задать конкретное его имя. Если необходимо найти какое-нибудь словосочетание, то нам на помощь приходят двойные кавычки. При заключении двух и более слов в двойные кавычки происходит поиск всей фразы целиком.

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

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

Дефолтное значение 5 можно изменить в настройках системы перейдя Stack Management > Advanced Settings

Заключение

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

Подробнее..

Перевод Обнаружение и удаление кода без ссылок с помощью ArchUnit

05.04.2021 12:20:40 | Автор: admin

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

ArchUnit

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

- Сайт Archunit

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

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

Репозиторий GitHub и структура тестов

Кэтому посту прилагается репозиторий GitHub.Он содержитминимальное приложение Spring Boot, а такжеправила ArchUnit для поиска классов и методов, на которые нетссылок.

На момент написаниямы использовали ArchUnit 0.15.0.В частности, мы используемподдержку JUnit5черезcom.tngtech.archunit:archunit-junit5:0.15.0.Это приводит к базовой структуре теста:

@AnalyzeClasses(  packagesOf = ArchunitUnusedRuleApplication.class,  importOptions = {    ImportOption.DoNotIncludeArchives.class,    ImportOption.DoNotIncludeJars.class,    ImportOption.DoNotIncludeTests.class})class ArchunitUnusedRuleApplicationTests {  @ArchTest  static ArchRule classesShouldNotBeUnused = classes()    .that()      ...    .should(      ...    );  @ArchTest  static ArchRule methodsShouldNotBeUnused = methods()    .that()      ...    .should(      ...    );}

Обратите внимание, как мы ограничиваем классы, анализируемые с помощью аргументов packagesOf иimportOptionsв аннотации @AnalyzeClasses().Кроме того, использование аннотации @ArchTest встатическом поле типа ArchRule избавляет нас от необходимости вызывать ArchRule.check(JavaClasses),как показано в предыдущем посте в блоге.

Спомощью селекторов classes() и methods() из ArchRuleDefinition выбираем элементы, которыемы хотим проверить с помощью нашего правила. Обычно эти элементы затем дополнительно ограничиваются последовательностью вызовов после.that(), чтобы отсеять потенциально ложные срабатывания. Наконец, с помощью.should()мы проверяем, что все оставшиеся классы удовлетворяют заданному условию, и при обнаружении каких-либо классов вызываем исключение.

Обнаружение неиспользуемых методов

Имея указанную выше базовую структуру, мы можем приступить к определению нашего первого правила для обнаружения неиспользуемых методов.Как мы уже говорили ранее, ArchUnit строит граф зависимостей между блоками кода Java, который мы можем использовать для поиска блоков кода, на которые никогда не ссылаются другие блоки кода Java.

Конечно, в типичном приложении Spring Boot есть причины, по которым метод никогда не вызывается напрямую, в частности, когда аннотируется конечная точка сети, прослушиватель сообщений или обработчик команд, событий или исключений.В этих случаях фреймворк будет вызывать методы за вас, поэтому вы не хотите, чтобы они случайно помечались как неиспользуемые в вашем правиле тестирования.То же самое касается общих методов, добавляемых, в частности, при использовании Lombok's@Dataor@Value, которые добавляютequals,hashCodeиtoStringв классы.

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

@ArchTeststatic ArchRule methodsShouldNotBeUnused = methods()  .that().doNotHaveName("equals")  .and().doNotHaveName("hashCode")  .and().doNotHaveName("toString")  .and().doNotHaveName("main")  .and().areNotMetaAnnotatedWith(RequestMapping.class)  .and(not(methodHasAnnotationThatEndsWith("Handler")    .or(methodHasAnnotationThatEndsWith("Listener"))    .or(methodHasAnnotationThatEndsWith("Scheduled"))    .and(declaredIn(describe("component", clazz -> clazz.isMetaAnnotatedWith(Component.class))))))  .should(new ArchCondition<>("not be unreferenced") {    @Override    public void check(JavaMethod javaMethod, ConditionEvents events) {      Set<JavaMethodCall> accesses = new HashSet<>(javaMethod.getAccessesToSelf());      accesses.removeAll(javaMethod.getAccessesFromSelf());      if (accesses.isEmpty()) {        events.add(new SimpleConditionEvent(javaMethod, false, String.format("%s is unreferenced in %s",          javaMethod.getDescription(), javaMethod.getSourceCodeLocation())));      }    }  });static DescribedPredicate<JavaMethod> methodHasAnnotationThatEndsWith(String suffix) {  return describe(String.format("has annotation that ends with '%s'", suffix),   method -> method.getAnnotations().stream()     .anyMatch(annotation -> annotation.getRawType().getFullName().endsWith(suffix)));}

Обнаружение неиспользуемых классов

Чтобы обнаружить целые классы, на которые нет ссылок из других классов, мы можем применить тот же подход с несколькими незначительными изменениями.Мы также не хотели бы ошибочно идентифицировать любой@Componentобъект, содержащий конечную точку, прослушиватель (listener) или обработчик, поэтому нам нужен еще один настраиваемый предикат.В нашей проверке состояния мы также контролируем обнаружение в JavaClass.getDirectDependenciesToSelf() каких-либо зависимостей, чтобы отсеять еще один источник ложных срабатываний.

В конце концов, мы получаем следующее правило ArchRule для поиска неиспользуемых классов.

@ArchTeststatic ArchRule classesShouldNotBeUnused = classes()  .that().areNotMetaAnnotatedWith(org.springframework.context.annotation.Configuration.class)  .and().areNotMetaAnnotatedWith(org.springframework.stereotype.Controller.class)  .and(not(classHasMethodWithAnnotationThatEndsWith("Handler")    .or(classHasMethodWithAnnotationThatEndsWith("Listener"))    .or(classHasMethodWithAnnotationThatEndsWith("Scheduled"))    .and(metaAnnotatedWith(Component.class))))  .should(new ArchCondition<>("not be unreferenced") {    @Override    public void check(JavaClass javaClass, ConditionEvents events) {      Set<JavaAccess<?>> accesses = new HashSet<>(javaClass.getAccessesToSelf());      accesses.removeAll(javaClass.getAccessesFromSelf());      if (accesses.isEmpty() && javaClass.getDirectDependenciesToSelf().isEmpty()) {        events.add(new SimpleConditionEvent(javaClass, false, String.format("%s is unreferenced in %s",          javaClass.getDescription(), javaClass.getSourceCodeLocation())));      }    }  });static DescribedPredicate<JavaClass> classHasMethodWithAnnotationThatEndsWith(String suffix) {  return describe(String.format("has method with annotation that ends with '%s'", suffix),    clazz -> clazz.getMethods().stream()      .flatMap(method -> method.getAnnotations().stream())      .anyMatch(annotation -> annotation.getRawType().getFullName().endsWith(suffix)));}

Ограничения

Хотя приведенные выше правила являются отличной отправной точкой для выявления потенциально неиспользуемого кода, к сожалению, именно здесь мы также начнем сталкиваться с некоторыми (текущими) ограничениями ArchUnit.В зависимости от того, как настроен ваш проект, вы можете обнаружить, чтоссылка на метод не рассматривается как зависимость.Или вы можете обнаружить,чтоаргументы шаблонного типане определены как зависимости.И, поскольку ArchUnit работает с байтовым кодом, вы можете обнаружить, чтостроковые константы установлены во время компиляции.

Замораживание ложных (или истинных!) срабатываний

К счастью, есть элегантный способ обработки ложных срабатываний в отношении наших пользовательских условий ArchConditions:Freezing Arch Rules.Передав наше правило ArchRule, в FreezingArchRule.freeze(ArchRule) мы можем записать все текущие нарушения и остановить добавление новых нарушений.

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

- Сайт Archunit

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

Протестируйте сами правила ArchUnit

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

@Nestedclass VerifyRulesThemselves {  @Test  void verifyClassesShouldNotBeUnused() {     JavaClasses javaClasses = new ClassFileImporter()       .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_ARCHIVES)       .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS)       .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)       .importPackagesOf(ArchunitUnusedRuleApplication.class);     AssertionError error = assertThrows(AssertionError.class,       () -> classesShouldNotBeUnused.check(javaClasses));     assertEquals("""       Architecture Violation [Priority: MEDIUM] - Rule 'classes that are not meta-annotated with @Configuration and are not meta-annotated with @Controller and not has method with annotation that ends with 'Handler' or has method with annotation that ends with 'Listener' or has method with annotation that ends with 'Scheduled' and meta-annotated with @Component should not be unreferenced' was violated (3 times):       Class <com.github.timtebeek.archunit.ComponentD> is unreferenced in (ArchunitUnusedRuleApplication.java:0)       Class <com.github.timtebeek.archunit.ModelF> is unreferenced in (ArchunitUnusedRuleApplication.java:0)       Class <com.github.timtebeek.archunit.PathsE> is unreferenced in (ArchunitUnusedRuleApplication.java:0)""",       error.getMessage());  }  @Test  void verifyMethodsShouldNotBeUnused() {    JavaClasses javaClasses = new ClassFileImporter()      .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_ARCHIVES)      .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS)      .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)      .importPackagesOf(ArchunitUnusedRuleApplication.class);    AssertionError error = assertThrows(AssertionError.class,      () -> methodsShouldNotBeUnused.check(javaClasses));    assertEquals("""      Architecture Violation [Priority: MEDIUM] - Rule 'methods that do not have name 'equals' and do not have name 'hashCode' and do not have name 'toString' and do not have name 'main' and are not meta-annotated with @RequestMapping and not has annotation that ends with 'Handler' or has annotation that ends with 'Listener' or has annotation that ends with 'Scheduled' and declared in component should not be unreferenced' was violated (2 times):      Method <com.github.timtebeek.archunit.ComponentD.doSomething(com.github.timtebeek.archunit.ModelD)> is unreferenced in (ArchunitUnusedRuleApplication.java:102)      Method <com.github.timtebeek.archunit.ModelF.toUpper()> is unreferenced in (ArchunitUnusedRuleApplication.java:143)""",      error.getMessage());  }}

Заключение

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

Подробнее..

Перевод Запись событий Spring при тестировании приложений Spring Boot

24.02.2021 18:11:35 | Автор: admin

Одна из основных функций Spring - функция публикации событий.Мы можем использовать события для разделения частей нашего приложения и реализации шаблона публикации-подписки.Одна часть нашего приложения может публиковать событие, на которое реагируют несколько слушателей (даже асинхронно).В рамкахSpring Framework 5.3.3(Spring Boot 2.4.2) теперь мы можем записывать и проверять все опубликованные события (ApplicationEvent) при тестировании приложений Spring Boot с использованием@RecrodApplicationEvents.

Настройка для записи ApplicationEvent с помощью Spring Boot

Чтобы использовать эту функцию, нам нужен толькоSpring Boot Starter Test,который является частью каждого проекта Spring Boot, который вы загружаете наstart.spring.io.

<dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-test</artifactId>  <scope>test</scope></dependency>

Обязательно используйте версию Spring Boot >= 2.4.2, так как нам нужна версия Spring Framework >= 5.3.3.

Для наших тестов есть одно дополнительное требование: нам нужно работать со SpringTestContextпоскольку публикация событий является основной функциональностью платформыApplicationContext.

Следовательно, она не работает для модульного теста, где неиспользуется поддержка инфраструктуры Spring TestContext.Есть несколькоаннотаций тестовых срезов Spring Boot,которые удобно загружают контекст для нашего теста.

Введение в публикацию событий Spring

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

public class UserCreationEvent extends ApplicationEvent {   private final String username;  private final Long id;   public UserCreationEvent(Object source, String username, Long id) {    super(source);    this.username = username;    this.id = id;  }   // getters}

Начиная со Spring Framework 4.2, нам не нужно расширять абстрактныйкласс ApplicationEventи мы можем использовать любой POJO в качестве нашего класса событий.В следующий статье привеленоотличное введение в события приложенийс помощью Spring Boot.

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

@Servicepublic class UserService {   private final ApplicationEventPublisher eventPublisher;   public UserService(ApplicationEventPublisher eventPublisher) {    this.eventPublisher = eventPublisher;  }   public Long createUser(String username) {    // logic to create a user and store it in a database    Long primaryKey = ThreadLocalRandom.current().nextLong(1, 1000);     this.eventPublisher.publishEvent(new UserCreationEvent(this, username, primaryKey));     return primaryKey;  }   public List<Long> createUser(List<String> usernames) {    List<Long> resultIds = new ArrayList<>();     for (String username : usernames) {      resultIds.add(createUser(username));    }     return resultIds;  }}

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

Например, наше приложение выполняет две дополнительные операции всякий раз, когда мы запускаем такоеUserCreationEvent:

@Componentpublic class ReportingListener {   @EventListener(UserCreationEvent.class)  public void reportUserCreation(UserCreationEvent event) {    // e.g. increment a counter to report the total amount of new users    System.out.println("Increment counter as new user was created: " + event);  }   @EventListener(UserCreationEvent.class)  public void syncUserToExternalSystem(UserCreationEvent event) {    // e.g. send a message to a messaging queue to inform other systems    System.out.println("informing other systems about new user: " + event);  }}

Запись и проверка событий приложения с помощью Spring Boot

Давайте напишем наш первый тест, который проверяет,UserServiceгенерирует событие всякий раз, когда мы создаем нового пользователя.Мы инструктируем Spring фиксировать наши события с помощью@RecordApplicationEventsаннотации поверх нашего тестового класса:

@SpringBootTest@RecordApplicationEventsclass UserServiceFullContextTest {   @Autowired  private ApplicationEvents applicationEvents;   @Autowired  private UserService userService;   @Test  void userCreationShouldPublishEvent() {     this.userService.createUser("duke");     assertEquals(1, applicationEvents      .stream(UserCreationEvent.class)      .filter(event -> event.getUsername().equals("duke"))      .count());     // There are multiple events recorded    // PrepareInstanceEvent    // BeforeTestMethodEvent    // BeforeTestExecutionEvent    // UserCreationEvent    applicationEvents.stream().forEach(System.out::println);  }}

После того, какмы выполняем публичный метод нашего класса испытываемый (createUserизUserServiceв этом примере), мы можем запросить все захваченные события из биновApplicationEvents,которые мы внедряем в наш тест.

Открытый.stream()методкласса ApplicationEventsпозволяет просмотреть все события, записанные для теста.Есть перегруженная версия .stream(),вкоторой мы запрашиваем поток только определенных событий.

Несмотря на то, что мы генерируем только одно событие из нашего приложения, Spring захватывает четыре события для теста выше.Остальные три события относятся к Spring, как иPrepareInstanceEventв среде TestContext.

Поскольку мы используем JUnit Jupiter иSpringExtension(зарегистрированный для нас при использовании@SpringBootTest), мы также можем внедритьbean-компонент ApplicationEventsв метод жизненного цикла JUnit или непосредственно в тест:

@Testvoid batchUserCreationShouldPublishEvents(@Autowired ApplicationEvents events) {  List<Long> result = this.userService.createUser(List.of("duke", "mike", "alice"));   assertEquals(3, result.size());  assertEquals(3, events.stream(UserCreationEvent.class).count());}

ЭкземплярApplicationEventsсоздается до и удаляется после каждого теста как часть текущего потока.Следовательно, вы даже можете использовать внедрение поля и@TestInstance(TestInstance.Lifecycle.PER_CLASS)делить тестовый экземпляр между несколькими тестами (PER_METHODпо умолчанию).

Обратите внимание, что запуск всего контекста Spring@SpringBootTestдля такого тестаможет быть излишним.Мы также могли бы написать тест, который заполняет минимальный SpringTestContextтолько нашимbean-компонентом UserService, чтобы убедиться, чтоUserCreationEvent опубликован:

@RecordApplicationEvents@ExtendWith(SpringExtension.class)@ContextConfiguration(classes = UserService.class)class UserServicePerClassTest {   @Autowired  private ApplicationEvents applicationEvents;   @Autowired  private UserService userService;   @Test  void userCreationShouldPublishEvent() {     this.userService.createUser("duke");     assertEquals(1, applicationEvents      .stream(UserCreationEvent.class)      .filter(event -> event.getUsername().equals("duke"))      .count());     applicationEvents.stream().forEach(System.out::println);  }}

Или используйте альтернативный подход к тестированию.

Альтернативы тестированию весенних событий

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

@ExtendWith(MockitoExtension.class)class UserServiceUnitTest {   @Mock  private ApplicationEventPublisher applicationEventPublisher;   @Captor  private ArgumentCaptor<UserCreationEvent> eventArgumentCaptor;   @InjectMocks  private UserService userService;   @Test  void userCreationShouldPublishEvent() {     Long result = this.userService.createUser("duke");     Mockito.verify(applicationEventPublisher).publishEvent(eventArgumentCaptor.capture());     assertEquals("duke", eventArgumentCaptor.getValue().getUsername());  }   @Test  void batchUserCreationShouldPublishEvents() {    List<Long> result = this.userService.createUser(List.of("duke", "mike", "alice"));     Mockito      .verify(applicationEventPublisher, Mockito.times(3))      .publishEvent(any(UserCreationEvent.class));  }}

Обратите внимание, что здесь мы не используем никакой поддержки Spring Test иполагаемсяисключительно наMockitoи JUnit Jupiter.

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

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)class ApplicationIT {   @Autowired  private TestRestTemplate testRestTemplate;   @Test  void shouldCreateUserAndPerformReporting() {     ResponseEntity<Void> result = this.testRestTemplate      .postForEntity("/api/users", "duke", Void.class);     assertEquals(201, result.getStatusCodeValue());    assertTrue(result.getHeaders().containsKey("Location"),      "Response doesn't contain Location header");     // additional assertion to verify the counter was incremented    // additional assertion that a new message is part of the queue  }}

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

Резюме тестирования событий Spring с помощью Spring Boot

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

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

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

Исходный код со всеми альтернативными вариантами для тестирования Spring Event с помощью Spring Bootдоступен на GitHub.

Подробнее..
Категории: Events , Java , Testing , Spring boot

Перевод Как найти все битые ссылки на странице с помощью Selenium

02.06.2021 14:09:29 | Автор: admin

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

Теперь с помощью этого java-кода вы можете проверить все ссылки. Эти ссылки могут быть ссылками pdf, изображения, видео или фотографии.

Шаг 1: В HTML мы связываем ссылки с помощью этого кода: <a href="Adress"></a> это означает, что мы должны собрать все ссылки на веб-странице на основе <a>. Для этого мы используем этот код:

List<WebElement> allLinks = driver.findElements(By.tagName(LINKS_TAG));

LINKS_TAG - это "a". В конце страницы я добавлю весь код.

Шаг 2: Определение и проверка URL-адреса

String urlLink = link.getAttribute(LINKS_ATTRIBUTE);

LINKS_ATTRIBUTE - это "href"

Шаг 3: Отправка HTTP-запроса и считывание кодов HTTP-ответов

Мы создаем HttpConnection с параметром URL. Я добавил также Connection Timeout.

URL url = new URL(urlLink);HttpURLConnection httpURLConnect=(HttpURLConnection)url.openConnection();httpURLConnect.setConnectTimeout(5000);httpURLConnect.connect();
  • Информационные коды ответов: 100-199

  • Коды успешного ответа: 200-299

  • Редирект коды: 300-399

  • Коды ошибок клиента: 400-499

  • Коды ошибок сервера: 500-599

В принципе, мы можем сказать, что если код ответа больше или равен 400, то в этом случае соединение прервано.

import org.openqa.selenium.By;import org.openqa.selenium.WebDriver;import org.openqa.selenium.WebElement;import org.openqa.selenium.chrome.ChromeDriver;import org.openqa.selenium.chrome.ChromeOptions;import org.testng.annotations.AfterClass;import org.testng.annotations.BeforeTest;import org.testng.annotations.Test;import java.net.HttpURLConnection;import java.net.URL;import java.util.List;public class FindAllBrokenLinks {    public final String DRIVER_PATH = "Drivers/chromedriver";    public final String DRIVER_TYPE = "webdriver.chrome.driver";    public WebDriver driver;    public final String BASE_URL = "https://www.bbc.com/";    public final String LINKS_ATTRIBUTE = "href";    public final String LINKS_TAG = "a";    @BeforeTest    public void beforeTest(){        ChromeOptions options = new ChromeOptions();        options.addArguments("--disable-notifications","--ignore-certificate-errors","--disable-extensions");        System.setProperty(DRIVER_TYPE,DRIVER_PATH);        driver = new ChromeDriver(options);        driver.manage().window().maximize();        driver.get(BASE_URL);    }    @Test    public void FindAllBrokenLinks() throws Exception{        List<WebElement> allLinks = driver.findElements(By.tagName(LINKS_TAG));        for(WebElement link:allLinks){            try {                String urlLink = link.getAttribute(LINKS_ATTRIBUTE);                URL url = new URL(urlLink);                HttpURLConnection httpURLConnect=(HttpURLConnection)url.openConnection();                httpURLConnect.setConnectTimeout(5000);                httpURLConnect.connect();                if(httpURLConnect.getResponseCode()>=400)                {                    System.out.println(urlLink+" - "+httpURLConnect.getResponseMessage()+"is a broken link");                }                else{                    System.out.println(urlLink+" - "+httpURLConnect.getResponseMessage());                }            }catch (Exception e) {            }        }    }    @AfterClass    public void CloseDriver(){        driver.close();    }}

Я использовал URL веб-страницы BBC в качестве базового URL, но запуск этого кода занял 1 минуту и 49 секунд. :) Возможно, вам стоит выбрать другой сайт.

Вот некоторые результаты тестов:

https://www.bbc.com/sport OK

https://www.bbc.com/reel OK

https://www.bbc.com/worklife OK

https://www.bbc.com/travel Временно приостановил работу

https://www.bbc.com/future OK

https://www.bbc.com/culture OK

https://www.bbc.com/culture/music OK

http://www.bbc.co.uk/worldserviceradio/ Не доступен

http://www.bbc.co.uk/programmes/p00wf2qw Не доступен

https://www.bbc.com/news/world-europe-57039362 OK


Перевод подготовлен в рамках набора учащихся на курс "Java QA Automation Engineer". Если вам интересно узнать о курсе подробнее, а также познакомиться с преподавателем, приглашаем на день открытых дверей онлайн.

Подробнее..

А вы все еще генерируете данные руками? Тогда GenRocket идет к вам

06.01.2021 20:05:30 | Автор: admin

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

НО изначально необходимо очертить проблему, которую можно решить при помощи этого сервиса.

Проблема генерации тестовых данных

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

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

Почему "необходимых"? А тут вспоминаются "Тестовое покрытие", которое говорит, что тестовые данные должны вести к покрытию максимально возможных сценариев.

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

Функциональность идеального сервиса генерации данных

Идеальный сервис по генерации тестовых данных должен иметь возможность:

  • генерировать данные в разных форматах (JSON, XML, CSV и т.д.)

  • генерировать данные с зависимостями (parent, child)

  • генерировать сложные зависиммые данные (if a then 1 or 2 else 3 or 5)

  • генерировать большие обьемы данный за небольшой промежуток времени

Хотелось бы иметь возможность:

  • загрузки данные в прямо в БД

  • интерграции в CI/CD

  • создавать модель данных автоматом из схем

GenRocket университет

Сервис GenRocket предоставяет тренинг-университет, пройдя который вы ознакомитесь с основным базовым функционалом, установкой и настройкой.

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

GenRocket сервис

GenRocket - это сервис для генерации данных, созданный в 2011 году Hycel Taylor и Garth Rose для решения проблемы создания реалистичных тестовых данных для любой модели данных. Сервис обладает функциональностью генерировать данные для автоматического тестирования, для тестирования нагрузки, тестирования безопасности и др.

Сервис состоит из двух частей: web часть и программная cli часть. В web части происходит создание сценария-инструкции для генерации данных, в программной cli части на самой машине происходит генерация данных.

Что бы начать работу с GenRocket пользователь должен быть авторизирован, затем что бы начать работу с GenRocket необходимо скачать архив для Runtime* для cli части, распаковать его и прописать в системных переменных путь к папке с GenRocket в переменную GEN_ROCKET_HOME и в переменно PATH прописать %GEN_ROCKET_HOME%\bin значение.

Затем открываем командную строку, набираем genrocket и видим картинку ниже.

GenRocket cli часть работает в двух режимах on-line и off-line, но для работы с off-line надо скачать сертификат, который будет валиден только 24 часа.

GenRocket домен и его атрибуты

Первые два из основных компонентов - это Домен и Атрибуты домена. Домен - это существительное, например, пользователь: адрес, кредитная карта и т.д. Каждый домен описывается атрибутами, например: имя, фамилия, e-mail, пароль и день рождения. На картинке ниже вы видите домент User (1), описанный атрибутами (2) и пример сгенерированных данных (3).

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

GenRocket генераторы

Следующий компонент - генератор (generator) - это функциональность, которая непосредственно отвечает за генерацию данных в различных форматах. Генераторов в GenRocket 150+ для различных типов данных. Например:

Каждому атрибуту домена GenRocket назначает свой генератор, опираясь на имя атрибута, автоматом. Например, для атрибута, который содержит слово Name, будет подобран генератор NameGen, а для атрибута с SSN будет подобран генератор SSNGen.

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

GenRocket получатель (receiver)

Следующий компонент - получатель (receiver) - это функциональность, которая отвечает за выгрузку данных в необходимом формате: XML, JSON, SQL, CSV, JDBC, REST, SOAP. В GenRocket 35+ подобных получателелей.

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

GenRocket сценарий (scenarios)

Следующий компонент - сценарий (scenarios) - это набор инструкций, которые определяют сколько и в каком порядке будут созданы данные. Сценарии бывают одиночные (2) и сценарии-цепочки (1), которые позволяют генерировать данные для несколько связанных доменов одновременно. За количество данных отвечает переменная loopCount в настройках домена. Причем у каждого домена значение этой переменной устаналивается отдельно, что позволяет генерировать разное количество данных для каждого домена в сценариях-цепочках.

Сценарий выгружается в виде grs файла (3) и должен быть исполнен на машине, где был установлен GenRocket. Открываем командную строку и выполняем сценарий при помощи команды genrocket -r UserInsertScenario.grs.

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

Применение GenRocket на реальном проекте

Возьмем небольшую схему данных, в которой есть таблицы user, grantHistory и notificationSetting.

Используя импорт DDL создадим домен для user.

create table `user` ( id int(10) not null auto_increment,  external_id varchar(50) not null unique,  first_name varchar(25) not null,  last_name varchar(25) not null,  middle_initial char(1),  username varchar(100) not null,  ssn varchar(15) not null,  password varchar(255) not null,  activation_date date,  primary key (id));

После создания доменов GenRocket подбирает подходящие генераторы для каждого атрибута. При необходимости настраиваем специфичные генераторы или модифицируем существующие. Например, изменяем generationType на random и сохраняем изменения.

Аналогичные действия проделываем для grant_history и notification_setting. Сгенерированные данные будут сохраняться в базу данных, для которой настроено JBDC соединение.

driver=org.h2.Driveruser=sapassword=saurl=jdbc:h2:file:~/lms_course/lms_alpha;AUTO_SERVER=TRUE;batchCount=1000

И так же для этой базы настраивается специфичные получатели H2InsertV2Receiver для вставки и SQLUpdateV2Receiver для модификации.

После всех манипуляций с настройками получаем файлы сценарии InsertScenarioChain.grs для вставки и UpdateScenarioChain.grs для модификации, после выполнения которых получаем картинку ниже.

И вуаля, данные в таблицах:

Заключение

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

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

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

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

Ниже расценка на доступ для GenRocket

Для сравнения я приготовила несколько ссылок бесплатных сервисов:

https://www.datprof.com/solutions/test-data-generation/ - только 14 дней бесплатного использования, похоже что цена договорная

http://generatedata.com/ - бесплатно, но возможна генерация только 100 записей

https://www.mockaroo.com/ - бесплатна возможна генерация только 1000 записей, остальное платно - для самого дорогого доступа $5000/year

Подробнее..

Категории

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

  • Имя: Макс
    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