TestCafe: Краткая история обретения гармонии в функциональном тестировании (с картинками)
TestCafe — это кросс-платформенный фреймворк для функционального тестирования веб-приложений, выпущенный недавно компанией DevExpress.
Мы в DevExpress занимаемся разработкой широкого спектра компонентов для веб-разработчиков (ASP.NET WebForms, ASP.NET MVC, а также JavaScript-компоненты и фреймворки) и, естественно, их тестируем. Если с юнит-тестами все в общем-то понятно и тут для каких-то невероятных откровений места не осталось, то с функциональными тестами ситуация далеко не так однозначна в силу сложности их реализации. Изначально фреймворк TestCafe был внутренней разработкой, нацеленной на решение насущных проблем с функциональным тестированием наших компонентов и сайтов. И так получилось, что эта внутренняя разработка в итоге выросла в самостоятельный продукт.
В этой статье я расскажу, каковы были предпосылки к созданию этого фреймворка, с какими проблемами (помимо фатального недостатка) мы столкнулись при тестировании существующими решениями и как попытались с ними справиться.
О насущном Проблема: В существующих тестовых решениях проблема заключается в необходимости установки браузерных плагинов. Настройка тестового окружения становится весьма нетривиальной — нужно установить плагины на браузеры, иногда настроить сами браузеры, плагин для новой версии браузера может просто банально не работать, а тестировать надо здесь и сейчас.
Часто выходит и так, что в большой команде разработчиков попросту не у всех разработчиков установлено тестовое окружение или установлена старая версия тестового фреймворка. И возникает такая ситуация, что в системе непрерывной интеграции падает тест, нужно срочно исправлять ошибку, а при этом значительная часть времени уходит только на то, чтобы тест «взлетел» у тебя локально. Обновление плагинов, фреймворка в системе непрерывной интеграции — отдельная веселая тема.
Также тут особняком стоят мобильные устройства. Для большинства конфигураций попросту нет плагинов, для каких-то — установка плагина далеко нетривиальна.
Основной «фишкой» TestCafe является то, что фреймворку для работы не требуются браузерные плагины и какие-то предварительные настройки браузера. В TestCafe используется plug«n"play подход для браузеров. Вы можете убедиться в этом, запустив демо-тест в браузере, в котором вы сейчас просматриваете эту статью. Если вы работаете на локальной машине, то TestCafe автоматом определит установленные браузеры и создаст воркера за вас:
Подключение мобильного устройства осуществляется так же просто, нажатием одной кнопки из браузера мобильного устройства или сканированием QR-кода:
Тут отдельно стоит упомянуть, что вся система управления имеет web-based интерфейс. Т.е. вы можете запускать тесты и анализировать их результаты на машине вашего коллеги (при условии, что вы находитесь в одной сети), просто подключив его браузер к фреймворку. При этом вашему коллеге ничего не придется устанавливать. Более того, такая гибкость дает возможность запускать тесты в любом удаленном окружении, например на виртуальных машинах Browser Stack.
Так же TestCafe не блокирует браузер и клавиатурный/мышиный ввод. Т.е. вы можете параллельно запустить тесты во всех имеющихся у вас браузерах и при этом продолжить работать в другой вкладке вашего любимого браузера.
Проблема:
Есть одна существенная разница между тестовым кодом и production кодом: как правило, тесты — это код, написанный единожды, но к этому коду мы часто возвращаемся для анализа нарушений в работе разрабатываемого приложения. Т.е. основное назначение тестового кода — выявление проблем, поэтому к нему возникают два требования:
детерминированность тестового сценария — тест должен осуществлять ровно один сценарий действий и этот сценарий должен быть легко понятен из кода теста; возможность быстро локализовать тот этап сценария, на котором возникла проблема. Эти соображения сильно отпечатались у меня в голове, когда однажды мы с коллегой отлаживали весьма сложный функциональный тест. Это была стена кода, из которой было трудно банально понять, что вообще происходит в тестовом сценарии. Поэтому мы пошагово отлаживали тест и сопоставляли видимые действия с кодом. У нас попросту ушло полчаса на то, чтобы понять, с каким вообще сценарием действий мы имеем дело!Да, тут проблема во многом человеческого фактора — тест был написан коряво и человек, создававший тест, не позаботился о тех, кто будет анализировать его результаты. Но, что поделать — «если человек может выстрелить себе в ногу, то рано или поздно он выстрелит себе в ногу».
Но т.к. исправлять всех людей — задача, мягко скажем, мало осуществимая, то в TestCafe мы не стали применять универсальный алгоритм поведения в сложных ситуациях[1]. Мы пошли несколько иным путем, немного пожертвовав временем разработчика тестов (напомним, что тест, как правило пишется единожды) в угоду тех, кто позже будет анализировать результаты работы данного теста.Тесты в TestCafe разбиты на шаги:
'@fixture Example'; '@page http://testcafe.devexpress.com/Example';
'@test'['Drag slider'] = { '1.Click label «I have tried TestCafe»': function () { act.click (': containsExcludeChildren (I have tried TestCafe)'); },
'2.Check initial slider value': function () { eq ($('#Developer_Rating').val (), 0); },
'3.Drag slider handle': function () { act.drag ('.ui-slider-handle', 360, 0); },
'4.Check resulting slider value': function () { eq ($('#Developer_Rating').val (), 7); } }; К шагам имеется два требования:
шаги должны быть именованными, т.е. разработчик должен описать естественным языком, что же происходит; каждый шаг должен содержать не более одного пользовательского действия. Скажу сразу, что этот подход был наиболее неоднозначно встречен нашими разработчиками и тестировщиками. Им хотелось писать код, а приходилось писать текст. Тем не менее, вскоре возмущения угасли, разработчики привыкли к подобному стилю. Зато теперь мы имеем хорошо задокументированные и структурированные тесты, а разработчики создают более осмысленный код тестов, т.к. предварительно они формулируют свои действия на естественном языке. При возникновении проблем более не требуется подробный анализ кода, суть теста можно просто прочитать, не вдаваясь в детали реализации.
Стоит также отметить, что TestCafe анализирует сетевой траффик и JavaScript ошибки. И если вдруг тест упал (не прошел assertion или возникла какая-то иная ошибка), фреймворк укажет на возможные причины падения — незагруженные ресурсы или ошибки в коде, что порою является весьма полезным при локализации проблемы.
Проблема:
Казалось бы, функциональный тест — это просто: выполняем эмуляцию действий конечного пользователя в некотором сценарии и оцениваем результаты. Но есть ряд скрытых механизмов, происходящих вне ведения пользователя, но влияющих на выполнение сценария. И разработчикам тестов приходится их учитывать.
Например, вы нажимаете на кнопку, при этом отправляется асинхронный XHR-запрос, в ответ приходят некие данные, из которых строится какой-нибудь блок страницы, который и отображается пользователю. Для пользователя это просто небольшая визуальная задержка, а для машины — это действие с побочными эффектами и недетерминированным временем завершения выполнения. И выходит так, что без углубления в смысл происходящего трудно написать хороший тест (ведь в тесте нам нужно явно указать, что необходимо дождаться завершения загрузки XHR-запроса и лишь затем переходить к следующему действию). Есть и другие варианты, когда внутренние механизмы имеют сильное влияние на прохождение тестов. Например, во многих фреймворках приходится вручную очищать cookie перед запуском тестов. Стоит забыть сделать такую очистку и можно получить весьма нестабильный и трудно отлаживаемый тест.
Помните о механизме тестовых шагов, о котором я упоминал ранее? Помимо улучшения структурированности кода он решает и другую задачу — позволяет нивелировать переходы между состояниями приложения. Т.е. в TestCafe не нужно явно прописывать вещи вроде waitForPageLoad () или waitForXhrComplete (). Т.к. каждый шаг содержит ровно одно действие, то TestCafe автоматически ожидает завершения перехода между страницами и окончания XHR-запросов, вызванных этим действием. Таким образом, вам не нужно описывать переходные состояния в тесте. Тест содержит ровным счетом лишь то, что должен содержать — эмуляцию пользовательских действий и анализ конечных состояний, к которым они приводят.Также мы позаботились об очистке cookie. Каждый тест по сути выполняется в некоем тестовом контейнере, который всегда на момент начала теста содержит пустые cookie. Вам не нужно заботиться о сбросе состояния и настройке браузерного тестового окружения. Это происходит автоматически.
Проблема:
Нередко тестируемая страница требует осуществления Basic или Windows-аутентификации. И часто данная проблема не имеет решения «из коробки».
TestCafe имеет встроенную поддержку этих протоколов аутентификации — просто укажите данные логина и TestCafe будет автоматически осуществлять аутентификацию, так же, как это делает ваш браузер:
Вы также можете указать данные логина в коде теста:
'@auth guest: MostlyHarmless';
'@test'['Click Input'] = { '1. Type name': function () { act.type (getInput (), 'Peter Parker'); },
'2. Move caret position': function () { act.click (getInput (), { caretPos: 5 }); },
'3. Erase a character': function () { act.press ('backspace'); },
'4. Check result': function () { eq (getInput ().val (), 'Pete Parker'); } }; Проблема:
API фреймворков функционального тестирования — вещь, заслуживающая отдельного внимания. Эти монстроузные надстройки над DOM, которые порою не поспевают за нововведениями в DOM API браузеров — далеко не самая простая штука для изучения.
«Зато тестерам, которые не знакомы с DOM и JavaScript, проще!» — скажете вы. Но, это утверждение содержит в себе внутреннее противоречие: это API, так или иначе, является оберткой для нативного DOM-дерева. Т.е. вам в любом случае нужно понимать структуру DOM и, более того, держать в голове дополнительное API и понимать, как одно проецируется в другое.
Как вы, наверное, уже заметили, языком создания тестов в TestCafe является JavaScript. Вернее, если уж быть точным, то это DSL, являющийся подмножеством JavaScript. Это значит, что любой существующий редактор JavaScript может без проблем открывать файлы тестов со всеми сопутствующими «плюшками», как то: подсветка синтаксиса, автодополнение и рефакторинги.Более того, нет более натурального языка для web«а, чем JavaScript. В TestCafe нет никакого промежуточного API, вы можете работать с DOM напрямую (это если вы большой поклонник Vanilla JS) или можете использовать автоматически подключаемую версию jQuery.
Отдельного внимание заслуживают доступные в TestCafe эмуляции действий конечного пользователя. Мы старались сохранить API настолько минималистичным, насколько это возможно, оставляя его при этом максимально гибким. Всего 11 методов, но они покрывают почти все встречавшиеся нам на практике сценарии.
Также API TestCafe включает 4 вида assertion’ов, умеющих распознавать типы объектов и производить «глубокое» сравнение. Для редактирования тестов вы можете использовать встроенный в фреймворк редактор:
Вы также можете использовать ваш любимый редактор JavaScript. TestCafe компилирует тесты «на лету», так что, если вы допустите синтаксическую ошибку или неправильно сформируете структуру теста, вы сразу же будете об этом оповещены:
Проблема:
Функциональные тесты могут быть нестабильны. Как правило, причиной является некорректно установленное время ожидания завершения эффектов от пользовательского действия. Изменения временных интервалов может вызывать множество факторов: задержки сети, медленный компьютер, медлительность самого тестируемого приложения. TestCafe, как упоминалось ранее, имеет встроенные механизмы ожидания переходов между страницами и окончания XHR-запросов. Однако, эвристики ожидания анимации пока что находятся на исследовательской стадии и не включены в релизную ветку фреймворка. Помимо этого, нестабильность теста может быть вызвана нестабильностью самого тестируемого приложения.
Для того чтобы отлавливать подобные тесты, в TestCafe введен специальный режим — Quarantine Mode, который в общем является технической реализацией идеи Мартина Фаулера о карантине для нестабильных тестов. Суть его достаточно проста: если тест падает, то он не сразу добавляется в список упавших тестов, а попадает в «карантин». Это значит, что тест будет прогоняться еще 2 раза и если результаты прохождения будут варьироваться, то такой тест будет добавлен в список нестабильных тестов в отчете.Этот довольно незатейливый механизм позволяет эффективно отлавливать тесты с недетерминированным результатом. Особенно хороший результат он дает при работе в системе непрерывной интеграции, где объем статистической выборки, естественно, значительно больше. К слову, в TestCafe имеется встроенное API для непрерывной интеграции. Фреймворк доступен как обычная node-библиотека через npm.
О рекордере тестов Конечно же, помимо всего описанного ранее, TestCafe имеет встроенный рекордер тестов с удобным live-редактором селекторов и возможностью откатывать произведенные действия:
В качестве заключения При разработке TestCafe мы использовали огромное количество open-source разработок, поэтому с нашей стороны было бы немного несправедливо не отдать должное open source сообществу. Так, в контексте разработки TestCafe, был создан parse5 — HTML-парсер[2], являющийся самым быстрым из полностью совместимых с HTML5-спецификацией парсеров для node.js. И whacko — форк библиотеки cheerio, использующий parse5 в качестве платформы.У нас еще огромное количество планов на будущее, которые мы стараемся максимально быстро реализовывать (мажорный релиз TestCafe выходит примерно каждые 2 месяца). В будущем, помимо очевидных улучшений юзабилити и скорости работы, нас ждут еще много уникальных интересных фич. Оставайтесь на связи!
Примечания 1. Универсальный алгоритм поведения в сложных ситуациях Декларировать тщетность бытия Припасть коленом к земле Закрыть лицо руками Заплакать
2. Знаете ли вы что…
В официальной спецификации HTML5 фигурирует тэг