Реклама в Telegram-каналах DzikPic и dev.by теперь дешевле. Узнать подробности 👨🏻‍💻
Support us

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

Оставить комментарий
Модульное тестирование кода на языке JavaScript: стратегии, библиотеки, инструменты

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

Этот процесс должен включать комплекс средств контроля качества и эффективности работы программиста. Такими средствами могут быть: модульное и интеграционное тестирование, непрерывная интеграция (Continuous Integration, CI), сбор и анализ разнообразных метрик (например, очень длинные методы в nDepend), проверка на соответствие требованиям JsLint, FxCop и пр.

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

Далее

Agile, TDD и BDD

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

Существуют также методики программирования, которые требуют начинать кодирование логики с написания модульного теста: Test-Driven Development (TDD) и Behavior-Driven Development (BDD). Они часто используются в Agile-процессе. Рассмотрим их особенности подробнее.

Test-Driven Development

Разработка через тестирование — это итеративный процесс написания кода, в котором повторяются следующие четыре шага:

Шаг 1. Перед тем, как добавить новый фрагмент логики, создайте модульный тест для проверки этой логики;

Шаг 2. Запустите тест и убедитесь в том, что он не проходит;

Шаг 3. Напишите самый простой код, который заставит тест выполниться успешно;

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

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

Test Doubles

Деление вспомогательных объектов, используемых при модульном тестировании, на категории берёт своё начало с книги xUnit Test Patterns Жерара Мезароса (Gerard Meszaros). Эти категории обобщённо называются «тестовые дублёры» (test doubles). Дублёры бывают следующих видов:

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

Mock — это вспомогательный объект, поведение которого задаётся заранее. Он используется для того, что имитировать интерфейс зависимого компонента и проверить в ходе теста, правильно ли он используется.

Spy — это вспомогательный объект для инспектирования вызываемых методов и передаваемых им параметров в ходе теста.

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

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

Разница между Stub и Mock заключается в способе проверки результатов работы теста. В случае со Stub в конце теста проверяется состояние объекта. В случае с Mock в ходе теста проверяется то, что объект используется именно так, как было описано при регистрации. Подробности можно узнать из заметки Mocks Aren't Stubs Мартина Фаулера (Martin Fowler), а я приведу тут лишь пример.

Stub Mock
"test connect should start polling": function () {
  this.client.url = "/my/url";
  sinon.stub(ajax, "poll").returns({});
  this.client.connect();
  sinon.assert.calledWith(ajax.poll, "/my/url");
}

 

"test connect should start polling": function () {
  this.client.url = "/my/url";
  var mock = sinon.mock(ajax)
  mock.expects("poll")
      .withArgs("/my/url")
      .returns({});
  this.client.connect();
  mock.verify();
}

 

Behavior-Driven Development

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

Шаг 1. Определение функциональных требований к реализуемому модулю в виде тестов;

Шаг 2. Кодирование модуля;

Шаг 3. Проверка того, что все пожелания заказчика или бизнес-аналитика (BA) выполнены путем проверки результатов запуска тестов.

При написании тестов в стиле BDD очень удобно использовать Mock-объекты из-за того, что они отлично отражают требования по функциональной части для компонента. Таким образом, тесты в процессе BDD могут служить формализованным представлением задачи (user story) в терминах Scrum, что позволяет экономить время на написании технического задания и документации по готовому продукту.

Каким должен быть фреймворк для модульного тестирования JavaScript?

Полноценный инструмент для модульного и интеграционного тестирования JavaScript должен состоять из следующих компонентов:

  • Assertion library (набор методов для проверки состояния компонента в конце каждого теста);
  • Mock library (инструмент для генерации Mock-объектов и других "дублёров");
  • Test runner (инструмент автоматического запуска тестов с поддержкой большинства браузеров, включая браузеры iOS и Android);
  • Блока подключения к популярным системам непрерывной интеграции (Continuous Integration).

Стратегии модульного тестирования кода на языке JavaScript

Сегодня существует три стратегии модульного тестирования JavaScript кода (подробнее — в третьей главе книги Test-Driven JavaScript Development Кристиана Йохансена (Christian Johansen)):

  • In-Browser тестирование;
  • Headless тестирование;
  • Тестирование по пути JsTestDriver.

In-Browser тестирование предполагает запуск всех модульных и интеграционных тестов из HTML-страницы, которую разработчик открывает в нужных браузерах самостоятельно. Такой подход прост и интуитивно понятен. Однако его минусом является то, что он не предусматривает возможности включения подобных тестов в Continuous Integration. Кроме того, запускать вручную HTML-страницу в десяти и более браузерах и постоянно нажимать "F5" может быть утомительно для разработчика.

Headless тестирование заключается в том, что весь JavaScript код тестируется на эмуляторе, который может быть написан на Java, Ruby, JavaScript, C++ и т.д. Самым известным эмулятором на сегодняшний день является PhantomJS, который представляет собой движок WebKit, запускаемый из командной строки. Из преимуществ эмулятора можно отметить то, что его легко можно использовать в Continuous Integration, а также то, что он позволяет автоматизировать запуск всех тестов из командной строки. Однако у такого подхода есть существенный недостаток — код не тестируется на реальных браузерах, поэтому есть риск пропустить ошибки браузера, которые не воспроизводятся на эмуляторе. До появления JsTestDriver можно было часто встретить то, что In-Browser тестирование комбинируется с Headless тестированием, поскольку они прекрасно дополняют друг друга.

JsTestDriver — это клиент-серверное приложение на Java для автоматического тестирования кода на языке JavaScript в реальных браузерах. В отличие от предыдущих двух стратегий JsTestDriver не имеет недостатков. А среди достоинств можно выделить следующие:

  • Тестирование кода в реальных браузерах;
  • Поддержка Continuous Integration;
  • Запуск всех тестов из командной строки;
  • Работа с любым числом браузеров, даже браузерами iOS и Android.

Такие возможности достигаются за счёт того, что система разделена на клиентскую и серверную части. Сервер JsTestDriver — это компонент, который следит за подключёнными к нему браузерами и использует их для запуска тестов по запросу клиента. Браузер подключается к серверу JsTestDriver вручную путём открытия страницы по адресу и порту, на котором сконфигурирован сервер. Клиентом может выступать расширение для IDE, командная строка или сервер Continuous Integration.

В настоящее время стиль JsTestDriver используется и в других системах запуска тестов для JavaScript, например, Karma или Buster.JS, а сам JsTestDriver считается устаревшим.

Сравнение популярных библиотек для модульного тестирования JavaScript

Мы провели сравнительный анализ трёх популярных библиотек для модульного тестирования JavaScript кода: Jasmine, qUnit и YUI Test. В первую очередь нас интересовало удобство написания тестов, отсутствие привязанности к DOM-модели браузера (на тот случай, если появится модуль для Node.js), поддержка асинхронных тестов и встроенные средства работы с Test Doubles.

  qUnit Jasmine YUI Test
Лицензия MIT MIT BSD
Зависимость от DOM-модели браузера Зависит. Также есть отдельная NPM-инсталляция для Node.js, не привязанная к браузеру Не зависит Включен в YUI library, а также поставляется как отдельная NPM-инсталляция, не привязанная к браузеру
Test Doubles Нет Есть поддержка Spy, имеются Fake-объекты для XmlHttpRequest и JavaScript setTimeout/setInterval Поддержка Mock-объектов (Y.Mock)
Асинхронные тесты Да, QUnit.asyncTest Да, каждый тест может принимать параметр done Да, каждый тест имеет методы wait и resume
Поддержка синтаксиса BDD Да, через расширение Да Нет

Классический синтаксис qUnit:

Классический синтаксис qUnit

Стиль BDD для qUnit:

qUnit BDD

Синтаксис Jasmine:

Синтаксис Jasmine

Синтаксис YUI Test:

Синтаксис YUI Test

Вывод: библиотека Jasmine удовлетворяет всем требованиям современного инструмента для написания модульных JavaScript тестов, а для расширения поддержки Mock-объектов можно использовать дополнительный компонент Sinon.js. Что касается YUI Test, то эта библиотека имеет некоторые синтаксические особенности, к которым необходимо привыкнуть. Кроме того, придётся привыкнуть и к отсутствию возможности создавать тесты в стиле BDD. А для тестирования XmlHttpRequest и таймеров JavaScript с помощью YUI Test придётся искать дополнительную библиотеку, которая включает соответствующие Fake-объекты. qUnit требует слишком много дополнительных компонентов для того, чтобы соответствовать Jasmine по возможностям. Однако для маленьких продуктов, например компонентов jQuery UI, qUnit подходит больше всего из-за своей простоты и встроенной функции генерации страницы с результатами выполнения тестов.

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

Мы провели сравнительный анализ трех популярных инструментов для запуска тестов на языке JavaScript: JsTestDriver, Karma и Buster.JS. Нас в первую очередь интересовала возможность автоматического запуска браузеров.

  JsTestDriver Karma Buster.JS
Лицензия Apache 2.0 MIT BSD 2-Clause
Платформа Java, распространяется в виде JAR-файла Node.js, устанавливается через NPM Node.js, устанавливается через NPM
Подключение браузеров Ручное или через параметр -browser при запуске сервера в командной строке Автоматическое через плагины, доступные в NPM Ручное, через модуль buster-server, автоматическое через модули buster-ci и buster-ci-agent
Continuous Integration Результат запуска тестов можно получить в XML, что позволяет внедрить в CI Поддержка Jenkins и TeamCity Результат запуска тестов можно получить в XML, что позволяет внедрить в CI
Транспорт XmlHttpRequest (см. третью главу книги Test-Driven JavaScript Development) XmlHttpRequest, WebSocket, JSONP, Flash (через библиотеку Socket.IO) XmlHttpRequest long polling, WebSocket, JSONP (через библиотеку Faye, которая используется модулем buster-server)
Подгрузка ресурсов (файлы CSS, HTML и др.) Да, ресурсы указываются в секции serve конфигурационного файла Да Да, для этого служит модуль buster-static
Поддержка Node.JS Нет Нет Да
Поддержка библиотек модульного тестирования qUnit или встроенная библиотека Jasmine, Mocha, qUnit Jasmine, qUnit или встроенная библиотека

Вывод: если для разрабатываемого проекта поддержка модульных тестов для Node.JS не является обязательным, то Karma выглядит оптимальным решением, благодаря опции автоматического запуска браузеров, большему набору модулей для настройки Continuous Integration, а также надёжности в работе по сравнению с JsTestDriver (об этом говорит главный разработчик Karma Войта Джина (Vojta Jina)).

Преимуществом Buster.JS является то, что данный инструмент может быть использован для модульных тестов как клиентской логики на языке JavaScript, так и сервера на Node.js.

Рассмотрим процесс установки и настройки Karma в ОС Windows. В первую очередь нужно установить Node.js. Затем можно приступить к загрузке модуля для Karma. Для этого нужно перейти в каталог, в который требуется выполнить установку, и в командной строке выполнить "npm install karma". Далее нужно перейти в дочерний каталог node_moduleskarmain, и выполнить команду "node karma init", чтобы запустить мастер генерации конфигурационного файла.

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

Конфигурация Karma

Старт локального сервера Karma и выполнение тестов запускаются командой "node karma start".

Запуск Karma

Заключение

Перечисленные в этой статье инструменты являются основными для организации эффективного модульного и интеграционного тестирования продукта, написанного на языке JavaScript. Однако кроме них существуют и другие средства. Например, сайт BrowserStack.com, который предлагает доступ к огромному количеству реальных браузеров для удалённого модульного тестирования. Также рекомендую обратить внимание на библиотеку Sinon.js, которая не только облегчает создание объектов Mock, Stub и Spy, но и включает широкий набор Fake-объектов для XmlHttpRequest, таймеров JavaScript и др.

Если говорить о том, как организовано модульное тестирование для продуктов IHS Connect и IHS Goldfire Cloud, то тут секрета нет: первый использует Jasmine и PhantomJS, а второй — Jasmine и Karma.

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



Титульное фото: www.motorauthority.com

Новый рекламный формат в наших телеграм-каналах.

Купить 500 символов за $150

Читайте также
10 популярных курсов по изучению JavaScript для крутой веб-разработки
10 популярных курсов по изучению JavaScript для крутой веб-разработки
10 популярных курсов по изучению JavaScript для крутой веб-разработки
JavaScript остается одним из самых популярных языков программирования в мире. Мы собрали список курсов и сертификаций по Javascript от основ до необычных особенностей. В листинге как платные, так и бесплатные онлайн-курсы. Погнали за новыми знаниями!
2 комментария
Как оплачиваются самые популярные языки GitHub и какой прогноз
Как оплачиваются самые популярные языки GitHub и какой прогноз
Как оплачиваются самые популярные языки GitHub и какой прогноз
Rust стал самым быстрорастущим языком по числу разработчиков
Rust стал самым быстрорастущим языком по числу разработчиков
Rust стал самым быстрорастущим языком по числу разработчиков
Бесплатные курсы по TypeScript, React, 3D разработке. По итогам могут взять на работу
Бесплатные курсы по TypeScript, React, 3D разработке. По итогам могут взять на работу
Бесплатные курсы по TypeScript, React, 3D разработке. По итогам могут взять на работу

Хотите сообщить важную новость? Пишите в Telegram-бот

Главные события и полезные ссылки в нашем Telegram-канале

Обсуждение
Комментируйте без ограничений

Релоцировались? Теперь вы можете комментировать без верификации аккаунта.

Комментариев пока нет.