В мире веб-разработки есть неписаное правило: поведение пользователей предсказать практически невозможно. Это способен подтвердить каждый, кто наблюдал за тем, как некто приступает к работе с новым для него сайтом или веб-приложением. Я, например, уже и не помню, сколько раз мне доводилось видеть подобное. Например, порой думается, что люди просто забывают о том, как пользоваться браузером на мобильнике. Иногда их действия настолько нелогичны, что кажется, будто перед тобой не реальный человек, работающий с реальным веб-сайтом, а пьеса в жанре театра абсурда: необычно, порой более чем, притягательно, есть над чем подумать, но… мы ведь не в театре.
Дело здесь в том, что разработчик не способен точно предсказать поведение пользователей в тот или иной момент работы с программой. Скажем, человек может быть весьма чем-то взволнован, в результате – попытается сделать что-то слишком быстро. То, как он вводит данные в поля и щёлкает по кнопкам, будет совсем непохоже на спокойное и сосредоточенное поведение. К сожалению, при проектировании и разработке программных продуктов, слишком часто ориентируются именно на подобный «идеальный вариант», а о том, что может произойти в других случаях, далёких от «идеала», не задумываются.
Разработчики стремятся создавать программные системы, надеясь на то, что пользователь поймёт их внутреннюю логику. Они видят пользователя рациональным, знающим, что, скажем, беспорядочные касания всего подряд на экране телефона могут привести к чему-нибудь крайне непредсказуемому. Странности, кстати, возможные и тогда, когда вполне обычный человек, случайно, возможно даже не глядя на экран компьютера или телефона, что-нибудь щёлкнет или чего-нибудь коснётся. Можете вспомнить, сколько раз вы сами так делали, скажем, разговаривая по телефону на ходу и параллельно пытаясь ответить на твит или электронное письмо?
Инструменты, которые позволяют исследовать реакцию компьютерных систем на непредсказуемые события, появились не вчера, но мы не будем слишком углубляться в историю. Рассмотрим близкий веб-разработчикам пример, когда в 2012-м году Netflix открыл код своего внутреннего сервиса Chaos Monkey. Эта система «выключает виртуальные машины и контейнеры, которые исполняются внутри продакшен-окружения». Говоря обычным языком, Chaos Monkey случайным образом отрубает сервера для того, чтобы можно было узнать, как поведёт себя система в подобном случае, убедиться, что весь сервис не рухнет в результате отказа нескольких машин.
То, что при проектировании приложений не стоит ограничиваться лишь их подготовкой к «идеальным вариантам», знают все. Но, если перевести эту проблему в плоскость разработки веб-интерфейсов, возникают вполне резонные вопросы: «Как найти непредсказуемые сбойные места и разрушительные для программы последовательности действий гипотетических пользователей? Если что-то подобное сделано для серверов, нельзя ли так же поступить с интерфейсами?».
Прежде чем ответить на этот вопрос, вспомним теорему о бесконечных обезьянах: «Абсолютно случайным образом ударяя по клавишам пишущей машинки, гипотетическая обезьяна рано или поздно напечатает одну из пьес Шекспира». Если это так, то всего одна обезьяна, чем бы она ни была, вполне способна найти баги и проблемы в веб-интерфейсе.
Обезьяны, в студию!
«Обезьянье тестирование» (monkey testing), или, как его ещё называют, «хаотическое тестирование», это методика испытания программных продуктов, основанная на имитации случайных действий пользователя, которые никак не связаны с реальными сценариями использования систем.
Если говорить о тестировании мобильных версий веб-сайтов, это могут быть случайные касания кнопок, разные жесты на сенсорном экране, ввод чего попало в поля. Цель такого тестирования – найти проблемы приложения или полностью нарушить его работоспособность. Это совсем не похоже на обычное модульное или приёмочное тестирование, когда пишут тестовые сценарии, содержащие наборы действий, происходящих в определённой последовательности и при определённых условиях. В итоге такие вот тесты, например, в сфере интерфейсов, сильно зависят от того, как разработчик видит их правильную работу.
Программисты обладают меньшим контролем над тем, как выполняются обезьяньи тесты, и так как каждый их запуск, в общем случае, подвергает приложение новому случайному набору испытаний, тестирование не ограничивается одним сценарием, оно скорее представляет собой бесконечный набор непредсказуемых сценариев взаимодействия пользователя и программы.
Хотя этот тип тестирования доступен на большинстве платформ, на которых исполняются самые разные приложения, нельзя сказать, что в веб-среде обезьяньи тесты получили должное распространение. Например, в Android SDK имеется встроенное средство UI Exerciser Monkey, с помощью которого можно проверить приложение, задействовав большинство интерфейсных и системных событий. Глядя на Android, вполне логичным было бы желание найти подобный стандартный функционал, скажем, в разделах «средства разработчика» популярных браузеров, но там «обезьяны» пока не водятся. Ситуацию исправила команда Marmelab, создав JavaScript-библиотеку хаотического тестирования веб-интерфейсов Gremlins.js.
Гремлины идут!
Всё указывает на то, что при разработке Gremlins.js создателей библиотеки вдохновляла пара комедийных ужастиков Джо Данте. «Гремлинами» в библиотеке называются функции, которые способны устроить адский беспорядок на плохо спроектированной странице. Библиотечные «могваи» — это мирные сущности, главная задача которых – наблюдать за происходящим, сообщать о том, что они видят, поддерживать процесс тестирования. Есть здесь и «Гизмо». В библиотеке он играет роль механизма, останавливающего сеанс тестирования.
Для того, чтобы воспользоваться библиотекой, запустив стандартную процедуру случайного тестирования, достаточно буквально пары строк кода или нескольких кликов мышью. В то же время, при необходимости, испытания можно весьма тонко настроить. Для того, чтобы приступить к работе, можно избрать один из трёх путей: подключить Gremlins.js к странице в виде автономной библиотеки, подключить как модуль Require.js, запустить из букмарклета. Рассмотрим эти способы.
▍Автономная библиотека
Самый простой способ создать тестовую среду – включить библиотеку непосредственно в веб-страницу, которую нужно испытать. Благодаря такому подходу, gremlins
окажется в глобальном пространстве имён, библиотеку можно будет вызвать откуда угодно. Выглядит это так:
<script src="path/to/gremlins.min.js"></script>
<script type="javascript">
// Теперь гремлинов можно вызвать откуда угодно!
gremlins.createHorde().unleash();
</script>
▍Модуль Require.js
Если в вашем проекте используется Require.js, Gremlins.js можно импортировать в нужном месте, не затрагивая глобальное пространство имён.
require.config({
paths: {
gremlins: 'scripts/libraries/gremlins.min'
}
});
require(['gremlins'], function(gremlins) {
gremlins.createHorde().unleash();
});
▍Использование букмарклета
Если вы не хотите интегрировать библиотеку в свой проект, выбираете путь эпизодического тестирования, можете воспользоваться букмарклетом, который позволяет испытать любую страницу, открытую в браузере. Вот ссылка из официальной документации, откуда букмарклет можно добавить в панель закладок.
Эксперименты
Если выбран вариант использования библиотеки, предусматривающий её включение в страницы или импорт с помощью Require.js, можно приступать к экспериментам с гремлинами в коде. В нашем примере показан вызов gremlins.createHorde().unleash(). Давайте разберёмся с тем, что при этом происходит.
gremlins // Эй, библиотека!
.createHorde() // Создай мне обычную стаю гремлинов
.unleash(); // и тут же выпусти её.
В Gremlins.js предусмотрено пять «пород» (species в терминологии библиотеки) «гремлинов» — функций, которые реализуют различные варианты взаимодействия пользователя со страницей. Стандартная «стая» (horde), с настройками по умолчанию, включает в себя все пять пород. А именно, это следующие:
-
formFillerGremlin
– заполняет поля ввода данными, щёлкает по флажкам и радиокнопкам, взаимодействует с другими стандартными элементами форм.
-
clickerGremlin
– имитирует щелчки мыши повсюду в видимой области документа.
-
toucherGremlin
– имитирует касания сенсорного дисплея.
-
scrollerGremlin
– прокручивает документ.
-
typerGremlin
– случайным образом вводит данные с виртуальной клавиатуры.
Вызванные гремлины, производящие со страницей различные действия, оставляют на экране видимые следы. Кроме того, их действия, вместе с дополнительными данными, связанные с тем или иным видом гремлинов, логируются в JavaScript-консоли браузера. Выглядят эти записи примерно так:
gremlin formFiller input 5 in <input type="number" name="age">
gremlin formFiller input pzdoyzshh0k9@o8cpskdb73nmi.r7r in <input type="email" name="email">
gremlin clicker click at 1219 301
gremlin scroller scroll to 100 25
По умолчанию гремлины вызываются случайным образом с интервалом в 10 миллисекунд 1000 раз.
Как уже было сказано, в Gremlins.js имеются не только гремлины, которые вполне способны разорвать сбойную страницу на части. Есть здесь и мирные могваи:
-
alertMogwai
– предотвращает блокирование теста из-за вызова alert().
-
fpsMogwai
– сообщает о количестве кадров в секунду, которые выдаёт браузер.
-
gizmoMogwai
– прекращает испытания.
Например, могвай, наблюдающий за частотой кадров, может сообщить об ошибке, если FPS упадёт ниже 10. Выглядит это так:
mogwai fps 12.67
mogwai fps 23.56
err > mogwai fps 7.54 < err
mogwai fps 15.76
Благодаря протоколированию происходящего и визуализации действий гремлинов, результаты испытания можно проанализировать, и, при необходимости, принять меры по исправлению ошибок.
Фактически, даже без каких-либо настроек, Gremlins.js реализует весьма приличный набор тестов.
Продвинутые эксперименты
Если после использования тестов в стандартной конфигурации вы не добились того, чего хотели, существует достаточное количество способов настройки испытаний. Например, возможно, в каждом сеансе тестирования, вы хотите сконцентрировать усилия лишь на каком-то конкретном компоненте страницы вместо того, чтобы постоянно тестировать её всю.
Хотя нельзя настроить все виды гремлинов, можно ограничить toucherGremlin
, clickerGremlin
и formFillerGremlin
лишь определёнными областями страницы. В частности, при настройке мы предлагаем гремлину взглянуть на родителя того элемента, на который хотим его нацелить. Если данный элемент входит в список интересующих нас объектов, гремлин приступит к своему делу. В противном случае он попытается найти элемент, с которым можно взаимодействовать. Мы так же можем задать максимальное количество попыток поиска подходящего элемента, задавая параметр maxXbTries
.
gremlins.species.clicker().canClick(function(element) {
return $(element).parents('.my-component').length;
/**
Находится ли элемент, на котором этот гремлин собирается сымитировать щелчок мыши, в интересующей нас области? Если так – вернём true и позволим этот элемент протестировать. Если нет – предложим продолжить поиск подходящего элемента, вернув false.
**/
}).maxNbTries(5); // Гремлин предпримет до 5 попыток найти подходящий элемент
// Так же можно настраивать toucherGremlin и formFillerGremlin
gremlins.species.formFiller.canFillElement(/** код настройки **/);
gremlins.species.toucher.canTouch(/** код настройки **/);
Селекция гремлинов
Если стандартных возможностей библиотеки вам не хватает, в частности, вас не устраивают существующие породы гремлинов, это легко поправить. Вы можете вывести собственную породу гремлинов, научить их делать всё то, чего вы ожидаете от пользователя вашего проекта.
Например, вам нужно проверить, что произойдёт, если некто попытается отправить форму случайным образом в любой момент работы с её элементами. Можно понадеяться на то, что clickerGremlin
когда-нибудь щёлкнет по кнопке отправки, но лучше всего – создать собственного гремлина, который занимается исключительно отправкой формы. Это повысит шансы выполнения данной операции и даст нам контроль над тем, как она выполняется.
Для выведения новых пород гремлинов нужно немного разбираться в том, как создавать и настраивать события DOM в JavaScript. Поэтому давайте рассмотрим процесс создания гремлина, отправляющего форму.
gremlins.createHorde() // сначала создадим стаю
.allGremlins() // активируем всех гремлинов
.gremlin(function() {
// Зададим параметры нашего гремлина, который будет отправлять форму
var targetElement, availableForms;
availableForms = document.querySelectorAll('form'); // Возьмём все доступные на странице формы
targetElement = availableForms[Math.floor(Math.random()*availableForms.length)]; // Случайным образом выберем один из результатов предыдущей операции
// Создадим заготовку для события отправки
var evt = document.createEvent('HTMLEvents'); // Создадим контейнер события
evt.initEvent('submit'); // Определим событие как событие отправки
targetElement.dispatchEvent(evt); // Назначим событие нашей случайно выбранной форме
// Кроме того, мы хотим залогировать это событие, чтобы всё было как у стандартных гремлинов
console.log('gremlin submit ', targetElement);
})
.unleash();
Если вы хотите больше узнать про создание настраиваемых событий, взгляните на документацию Mozilla. Кроме того, стоит посмотреть на исходный код библиотеки, разобраться с тем, как в ней создаются события. Например, можно начать с clickerGremlin.
На самом деле, библиотека поддерживает куда больше способов настройки и расширения, нежели создание новых типов гремлинов. Здесь, например, и добавление новых пород могваев, и выбор сценариев тестирования, и исполнение кода до и после тестов. В конце концов, если вам хочется большего, на основе Gremlins.js можно создать собственный проект, который будет именно таким, каким вы видите JS-библиотеку тестирования интерфейсов.
Неслучайный выбор тестового сценария
Случайным образом выбираемые тестовые сценарии, новая стая гремлинов, появляющаяся после каждого запуска теста, и беспорядочно испытывающая страницу – это отличный способ поиска ошибок. Однако, если ошибка найдена, и вы поработали над её устранением, как убедиться в том, что она и вправду исправлена? В подобной ситуации хорошо бы иметь способ повторить тот тест, который её вызвал, узнать новую реакцию системы на него. Как раз для таких случаев, когда нужно несколько раз повторить один и тот же тестовый сценарий, существует вариант инициализации генератора случайных чисел, на основе которого моделируется поведение стаи. Делается это при помощи функции seed()
:
var horde = gremlins.createHorde();
horde.seed(1234);
horde.unleash();
Здесь хотелось бы отметить, что главная ценность обезьяньего тестирования, реализуемого с помощью Gremlins.js – это его непредсказуемость. Поэтому, хотя повтор одних и тех же сценариев полезен, использование лишь таких тестов ставит под вопрос сам смысл хаотического тестирования.
Выводы
Строить предположения о пользователях, основывать на них проектирование и разработку веб-проектов, означает заранее отказываться от учёта невероятного количества особых случаев. У пользователей могут быть медленные каналы связи, не самые новые устройства, да мало ли что и как может быть у конкретного человека. Нельзя гарантировать, что любопытный малыш лет пяти не доберётся до родительского телефона и не начнёт с увлечением тыкать во всё подряд на сенсорном дисплее. К чему это приведёт – большой вопрос. Нельзя заранее узнать о том, что некто из отдела маркетинга, впервые увидев новый сайт, не придёт в такой восторг, что станет, как безумный, щёлкать всё подряд. Ни порядок, ни скорость, ни повторяемость действий предсказать нельзя. Как разработчики, мы обязаны сделать для своих пользователей так, чтобы наши сервисы не валились ни с того ни с сего просто потому, что мы ожидаем от людей строго определённой модели правильного поведения.
Выбор средств обезьяньего тестирования на стороне клиента невелик, но разработчикам Gremlins.js удалось ухватить самую суть. И каждый из нас может внести вклад в улучшение этой библиотеки. Команда создателей Gremlins.js будет рада любой помощи. Дайте им знать, если у вас появятся соображения, касающиеся того, что ещё полезного смогут сделать гремлины и могваи.
Автор: RUVDS.com