Лекция
Привет, сегодня поговорим про unit тестирование javascript, обещаю рассказать все что знаю. Для того чтобы лучше понимать что такое unit тестирование javascript , настоятельно рекомендую прочитать все из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend).
Довольно распространенная проблема при написании модульных тестов для клиентского кода состоит в том, что его структура не подходит для тестирования. Ведь JavaScript код может быть написан для любой страницы сайта или модуля в приложении, также он может быть непосредственно связан с серверной логикой и с HTML кодом. В самом худшем случае код полностью привязан к HTML в качестве встроенных обработчиков событий.
Обычно такая ситуация происходит, когда разработчик для написания приложения не использует специальные JavaScript библиотеки. Еще бы, ведь написать встроенный обработчик события намного легче, чем привязать его же через DOM API. Однако большинство разработчиков все же используют специальные JavaScript библиотеки, например JQuery. JQuery позволяет помещать встроенные обработчики в отдельные сценарии или на той же странице, или в отдельном js файле. Но код, размещенный в отдельном файле, не является готовым к тестированию модулем.
Что же такое модуль? В лучшем случае это функция, которая всегда возвращает некоторый результат для некоторого параметра. Такой модуль тестировать очень легко. Конечно для выделения таких функций из существующего скрипта большую часть времени придется потратить на операции с деревом DOM. Однако такой подход поможет выделить из кода структурные единицы для создания модульных тестов.
Легче всего тестировать скрипт, если пишешь его с нуля. Но статья не об этом. В данной статье я постараюсь рассказать о том, как извлекать и тестировать наиболее важные части скрипта, а также выявлять и устранять ошибки.
Процесс изменения внутренней структуры программы без изменения ее поведения называется рефакторингом. Это отличный способ улучшения кода в проекте. Но любое изменение программного кода может изменить поведение программы, поэтому лучше всего запускать модульные тесты сразу после рефакторинга.
При добавление тестов к существующему коду, возможен риск нарушить его работу. Поэтому пока вы не освоились с юнит-тестами, старайтесь дополнительно проверять работу кода вручную.
Достаточно теориии! Разберу-ка я практический пример, тестируя конкретный JavaScript код, который встроен в html код страницы. Скрипт просматривает все ссылки с аттрибутом title. И использует значение этого аттрибута для преобразования текста ссылок в более человеческий вид: «n minutes ago», «n hours ago», «n weeks ago».
Если вы запустите этот скрипт, то увидите проблему: ни одна дате не заменилась. Хотя код при этом работает. Он проходит по всем ссылкам a и проверяет, есть ли у них title. Если он есть, то скрипт передает его значение функцие prettyDate. Если же она возвращает что-то, то скрипт заменяет innerHTMLссылки на этот результат.
Проблема в этом скрипте в том, что функция prettyDate для любой даты старше 31 дня возвращает undefined (с помощью return), оставляя тескт ссылки без изменений. А текущая дата намного старше 31-го дня. Поэтому, чтобы скрипт изменил текст ссылок, я жестко указал текущую дату:
В результате работы скрипта, появились ссылки: «2 часа назад», «вчера» и так далее. Это уже кое-что, но до тестируемого модуля не дотягивает. Об этом говорит сайт https://intellect.icu . Поэтому, без дальнейшего рефакторинга все, что я могу делать, это протестировать изменения в разметке.
При этом нужно создавать код с максимально чисто функцией (методами) , для того чтобы тесты былиь изолированы от окружения (БД, сеть, файловая система, время).
По всей видимости скрипт нуждается в 2 изменениях:
Содержимое файла prettydate.js:
Теперь можно писать модульный тест.
(Перед запуском убедитесь, что у вас включена консоль, например Firebug или Chrome’s Web Inspector.)
В результате получился модульный тест, который выводит результаты в консоль. Это позволяет избежать зависимости от DOM, поэтому тест можно запустить в небраузерной JavaScript среде, например Node.js или Rhino.
Скрипт выведет в консоль суммарную информацию: общее количество тестов, количество неудачных тестов, количество удачных тестов.
При успешном прохождении всех тестов, результат будет следующего вида:
Of 6 tests, 0 failed, 6 passed.
Если один из тестов не прошел, то он выведен в консоль в таком виде:
Expected 2 day ago, but was 2 days ago.
Of 6 tests, 1 failed, 5 passed.
Данный подход к разработке модульных тестов интересен как доказательство концепции: гораздо более практично использовать существующий фреймворк для тестирования, который обеспечит лучшую производительность и больше возможностей для написания и организации тестов.
Выбор фреймворка для тестирования является делом вкуса.
Я предпочитаю использовать QUnit, потому что у него много возможностей
Здесь стоит обратить внимание на 3 секции. Здесь подключены 3 файла: 2 файла это QUnit (qunit.css and qunit.js) и 1 файл prettydate.js.
Затем идет блок JavaScript кода с тестовыми данными. Метод test вызывается только один раз, передавая в качестве первого аргумента имя теста, а вторым аргментом является функция, которая фактически запускает тесты. В этом блоке объявляется переменная now, которая используется при вызове метода equal. Метод equal — это один из методов, которые предлагает QUnit. В качестве первого аргумента методу equal передается результат функции prettyDate. В качестве второго аргумента методу equal передается ожидаемый результат. Если эти два аргумента будут одинаковыми, то утверждение истинно, иначе не истинно.
При успешном тесте QUnit выведет результат следующего вида:
Если во время тестирования было не верное утверждение, то результат будет такого вида:
Поскольку на втором скриншоте один из тестов был неудачным, то QUnit не сворачивает лог тестирования и можно сразу увидеть, что пошло не так. Вместе с выводом ожидаемых и фактических результатов QUnit также показывает разницу между ними, которая очень полезна при сравнении больших строк. Здесь же довольно очевидно, что пошло не так.
Тестовых данных недостаточно, т.к. я не тестирую вариант n weeks ago. Но сначала я снова сделаю рефакторинг. Функция prettyDate вызывается для каждого утверждения и каждый раз передается аргумент now. Сейчас я это исправлю:
Теперь функция prettyDate вызывается внутри новой функции date и больше не создается переменная now, ее значение жестко прописано в качестве параметра.
Я уже достаточно хорошо поработал с функцией prettyDate. Теперь обратим внимание на первоначальный скрипт. В скрипте делалась выборка некоторых DOM-элементов, и они обновлялись в зависимости от результата функции prettyDate. Применяя те же принципы как и прежде, я сделаю рефакторинг и оттестирую его как следует) Кроме того, я создам модуль для этих 2 функций, и, чтобы избежать ошибок с глобальным пространством имен, дам им более осмысленные имена.
Содержание prettydate2.js:
Из базового скрипта я выделил новую функцию prettyDate.update, однако аргумент now все еще передается в prettyDate.format. Базовый QUnit-тест для этой функции начинается с отбора всех элементов a из блока div с идентификатором #qunit-fixture. В HTML коде появился новый элемент
…
. В нем содержится извлеченная разметка из начального примера, необходимая для тестирования. Помести ее в #qunit-fixture можно не беспокоиться о влиянии одного теста на результат другого, т.к. QUnit автоматически сбрасывает разметку после каждого теста.
Что происходит при первом тестировании prettyDate.update.
Сначала выбираются ссылки из
…
, далее они проверяются на содержание дат. Затем вызывается prettyDate.update, передавая фиксированную дату (такую же как и в предыдущих тестах). Потом происходит проверка текста этих же ссылок на желаемые значения «2 hours ago» и «Yesterday», т.к. prettyDate.update должен был заменить этот текст.
Следующий тест prettyDate.update, one day later делает почти тоже самое, за исключением того, что передает другую дату в prettyDate.update и потому возвращает другой результат для 2 ссылок.
Настало время для очередного рефакторинга! Необходимо убрать дублирование!
В этом примере я создал новую функцию domtest, которая инкапсулирует логику двух предыдущих вызовов для проверки и передает такие аргументы как имя теста, дату в строковом формате и две ожидаемые на выходе строки. Эта функция вызывается дважды.
Надо бы посмотреть, что получилось в итоге)
В результате рефакторинга было сделано множество усовершенствований по сравнению с первым примером. И благодаря модулю prettyDate я могу добавить еще больше функциональности без конфликтов в глобальном пространстве имен.
При тестировании JavaScript кода возникает вопрос не только в том, какой выбрать фреймворк или написать модуль самостоятельно. Обычно приходится делать большие структурные изменения для их применения к скрипту, который прежде проверялся вручную. Я очень надеюсь, что вы поняли хоть что-нибудь. Например, как изменять структуру существующего модуля для тестирования и как использоваеть более функциональный фреймворк, для получения более полезных результатов.
У фреймворк QUnit намного больше возможностей, чем я описал. Например, есть поддержка тестирования асинхронного кода: AJAX и события, тайм-ауты. Визуализация результатов помогает отлаживать код, облегчает повторный запуск специфичных тестов и предусматривает стек вызовов для неудачных утверждений и захваченных исключений. Для дальнейшего изучения QUnit фреймворка пройдите по ссылке QUnit Cookbook.
Надеюсь, эта статья про unit тестирование javascript, была вам полезна, счастья и удачи в ваших начинаниях! Надеюсь, что теперь ты понял что такое unit тестирование javascript и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)
Обычно я пишу свои JS-модули, используя шаблон модуля Revealing Module, т. Е. Выставляю только одни методы, оставляя другие внутренними для объекта. Я пытаюсь интегрировать написание тестов (в частности, jasmine.js) в свой рабочий процесс разработки, но до сих пор не могу понять, как применять тесты к моему частному коду. Был ли у вас опыт с этим? Во всех примерах, которые я видел, все тестируемые функции общедоступны.
PS. Пожалуйста, обновите ссылку для QUnit Cookbook (должно быть http://qunitjs.com/cookbook/ )
В настоящее время я участвую в модульном тестировании графического интерфейса, и с такими тяжеловесными вещами, как Java Swing, я в порядке, но мне тоже нужно что-то для мира HTML5 / Ajax / JS, и этот QUnit, кажется, является хорошим началом для меня. Идея хорошо понятна и позволяет даже новичку начать модульное тестирование кода JS. Отличная работа. Там только одна записка. ИМХО, «Рефакторинг, Стадия 1 скорее скрывает смысл, чем облегчает его понимание. Эта функция «дата не дает никакой реальной ценности, она просто мешает «сейчас куда-то еще. Я не уверен, что я даже ввел бы локальную переменную сейчас , и я почти уверен, что никогда не реорганизовал бы ее. Первая версия гораздо более читабельна для меня, и я предлагаю другим подумать, почему мы должны сделать тесты короче. Чем легче понять тест, тем лучше, особенно если сначала нужно написать тесты, а разрабатывать код только потом (разработка через тестирование или, что еще лучше, разработка через поведение). Насколько я вижу, имеет общее предположение, что он «нечитаемый и, следовательно, трудно поддерживаемый, в основном потому, что многие эксперты JS «сокращают свой код (то есть обфусцируют его).
Существуют и другие структуры для написания модульных тестов: https://github.com/moll/js-must https://github.com/visionmedia/should.js/ http://unitjs.com http: // nodejs .org / api / assert.html и многие другие
Комментарии
Оставить комментарий
Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)
Термины: Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)