Система тестирования на javascript. Тесты JavaScript и их автоматизация

И является официальным инструментом для тестирования jQuery. Но QUnit отлично подходит для тестирования любого кода JavaScript и даже способна тестировать серверную часть JavaScript с помощью механизмов наподобие Rhino или V8.

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

"Модульное тестирование или юнит-тестирование (англ. unit testing ) — процесс в программировании, позволяющий проверить на корректность отдельные модули исходного кода программы. Идея состоит в том, чтобы писать тесты для каждой нетривиальной функции или метода. Это позволяет достаточно быстро проверить, не привело ли очередное изменение кода к регрессии , то есть к появлению ошибок в уже оттестированных местах программы, а также облегчает обнаружение и устранение таких ошибок."

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

Зачем следует тестировать свой код

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

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

Во-вторых, все, что делается для такого тестирования, не может быть использовано повторно. При таком методе очень сложно найти регрессии. Что такое регрессии? Представьте, что вы написали некий код и протестировали его, исправили все ошибки, которые нашли, и поместили код на сайте. Затем пользователь прислал отзыв о новых ошибках и запрос на новые функции. Вы возвращаетесь к коду, исправляете ошибки и добавляете новые функции. При этом может возникнуть ситуация, когда старые ошибки проявляются снова, что называется "регрессией". Вам снова приходится все проверять. И есть шанс, что вы не найдете свои старые ошибки. В любом случае пройдет время, прежде чем вы догадаетесь, что проблема вызвана "регрессией". При использовании модульного тестирования вы пишите тест. Как только код модифицируется, вы снова фильтруете его через тест. Eсли регрессия проявляется, то какие-нибудь тесты не пройдут, и вы легко определите, какая часть кода содержит ошибку. Так как вы знаете, что изменили, то ошибку будет легко исправить.

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

Как писать тесты модулей в QUnit

Итак, как же непосредственно писать тесты модулей в QUnit? Первым шагом нужно установить среду тестирования:

Комплект для тестов QUnit Комплект для тестов QUnit

Код, который будет тестироваться, помещается в файл myProject.js , а тесты помещаются в myTests.js . Чтобы запустить тесты, нужно просто открыть HTML файл в браузере. Теперь пришло время написать какой-нибудь тест.

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

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

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

// протестируем данную функцию function isEven(val) { return val % 2 === 0; } test("isEven()", function() { ok(isEven(0), "Ноль - четное число"); ok(isEven(2), "Два - тоже"); ok(isEven(-4), "И отрицательное четыре - тоже четное число"); ok(!isEven(1), "Один - нечетное число"); ok(!isEven(-7), "Как и отрицательное семь - нечетное число"); })

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

Сначала мы вызываем функцию test() , которая строит блок теста. Первый параметр является строкой, которая будет выводиться в результате. Второй параметр - возвратная функция, которая содержит наши утверждения. Данная возвратная функция будет вызываться один раз при выполнении QUnit.

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

Вот что мы получим после выполнения теста:

Все наши утверждения успешно подтвердились, поэтому можно считать, что функция isEven() работает так, как ожидалось.

Давайте посмотрим, что случиться, если утверждение будет неверным.

// протестируем данную функцию function isEven(val) { return val % 2 === 0; } test("isEven()", function() { ok(isEven(0), "Ноль - четное число"); ok(isEven(2), "Два - тоже"); ok(isEven(-4), "И отрицательное четыре - тоже четное число"); ok(!isEven(1), "Один - нечетное число"); ok(!isEven(-7), "Как и отрицательное семь - нечетное число"); // Ошибка ok(isEven(3), "Три - четное число"); })

И вот что мы получим в результате выполнения теста:


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

Другие утверждения

ok() не является единственным утверждением, которое поддерживает QUnit. Существуют и другие типы утверждений, которые удобно использовать при составлении тестов для ваших проектов:

Утверждение сравнения

Утверждение сравнения equals() предполагает, что первый параметр (который является действительным значением) эквивалентен второму параметру (который является ожидаемым значением). Данное утверждение очень похоже на ok() , но выводит оба значения - действительное и предполагаемое, что существенно облегчает отладку кода. Также как и ok() , equals() в качестве третьего параметра может принимать сообщение для вывода.

Так вместо

Test("assertions", function() { ok(1 == 1, "один эквивалентно одному"); })


Следует использовать:

Test("assertions", function() { equals(1, 1, "один эквивалентно одному"); })


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

А если значения не равны:

Test("assertions", function() { equals(2, 1, "один эквивалентно одному"); })


Такая запись дает больше информации.

Утверждение сравнения использует оператор “==” для проверки параметров, поэтому оно не может работать с массивами или объектами:

Test("test", function() { equals({}, {}, "ошибка, это разные объекты"); equals({a: 1}, {a: 1} , "ошибка"); equals(, , "ошибка, это разные массивы"); equals(, , "ошибка"); })

Для таких случаев в QUnit есть утверждение идентичности.

Утверждение идентичности

Утверждение идентичности same() использует те же параметры, что и equals() , но работает не только с примитивными типами, а и с массивами и объектами. Утверждения из предыдущего примера пройдут проверку, если изменить из на утверждения идентичности:

Test("test", function() { same({}, {}, "проходит, объекты имеют одинаковый контент"); same({a: 1}, {a: 1} , "проходит"); same(, , "проходит, массивы имеют одинаковый контент"); same(, , "проходит"); })

Заметьте, что same() использует оператор ‘===’ для сравнения, поэтому его удобно использовать для сравнения специальных значений:

Test("test", function() { equals(0, false, "true"); same(0, false, "false"); equals(null, undefined, "true"); same(null, undefined, "false"); })

Структура утверждений

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

Можно организовывать отдельные модули с помощью вызова функции module :

Module("Модуль A"); test("Тест", function() {}); test("Еще один тест", function() {}); module("Модуль B"); test("Тест", function() {}); test("Еще один тест", function() {});


В предыдущем примере все утверждения вызывались синхронно, то есть выполнялись одно за другим. В реальном мире существует множество асинхронных функций, таких как запросы AJAX или функции setTimeout() и setInterval() . Как нам тестировать такой тип функций? QUnit имеет специальный тип тестов, который называется "асинхронный тест" и предназначен для асинхронного тестирования:

Сначала попробуем написать тест обычным способом:

Test("Асинхронный тест", function() { setTimeout(function() { ok(true); }, 100) })


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

Правильный вариант тестирования нашего примера:

Test("Асинхронный тест", function() { // Переводим тест в режим "пауза" stop(); setTimeout(function() { ok(true); // После вызова утверждения // продолжаем тест start(); }, 100) })


Мы использовали функцию stop() для остановки теста, а после выполнения утверждения снова запускали тест с помощью функции start() .

Вызов функции stop() сразу после вызова функции test() является весьма распространенной практикой. Поэтому QUnit имеет специальное сокращение: asyncTest() . Предыдущий пример можно переписать в виде:

AsyncTest("Асинхронный тест", function() { // Тест автоматически переводится в режим "пауза" setTimeout(function() { ok(true); // После вызова утверждения // продолжаем тест start(); }, 100) })

Есть один момент, над которым стоит задуматься: функция setTimeout() всегда вызывает свою возвратную функцию, а если тестировать другую функцию (например, вызов AJAX). Как быть уверенным, что возвратная функция будет вызвана? Если возвратная функция не будет вызвана, функция start() тоже останется без вызова и весь тест "подвиснет":


Можно организовать тест следующим образом:

// Пользовательская функция function ajax(successCallback) { $.ajax({ url: "server.php", success: successCallback }); } test("Асинхронный тест", function() { // Останавливаем тест и // будем сообщать об ошибке, если функция start() не будет вызвана по истечении 1 секунды stop(1000); ajax(function() { // ...асинхронное утверждение start(); }) })

В функцию stop() передается значение таймаута. Теперь QUnit получил указание: “если функция start() не будет вызвана по истечении таймаута, следует считать данный тест проваленным”. Теперь весь тест не "подвиснет" и будет выдано предупреждение, если что-то пойдет не так, как нужно.

Теперь рассмотрим случай множественных асинхронных функций. Где размещать функцию start() ? Нужно размещать ее в функции setTimeout() :

// Пользовательская функция function ajax(successCallback) { $.ajax({ url: "server.php", success: successCallback }); } test("Асинхронный тест", function() { // Останавливаем тест stop(); ajax(function() { // ...асинхронное утверждение }) ajax(function() { // ...асинхронное утверждение }) setTimeout(function() { start(); }, 2000); })

Значение таймаута должно быть достаточным для выполнения вызовов обеих возвратных функций перед продолжением теста. Если одна из функций не будет вызвана, как определить какая именно? Для этого есть функция expect() :

// Пользовательская функция function ajax(successCallback) { $.ajax({ url: "server.php", success: successCallback }); } test("Асинхронный тест", function() { // Останавливаем тест stop(); // Сообщаем QUnit, что мы ожидаем выполнения трех утверждений expect(3); ajax(function() { ok(true); }) ajax(function() { ok(true); ok(true); }) setTimeout(function() { start(); }, 2000); })

Мы передаем в функцию expect() количество утверждений, которые планируется выполнить. Если одно из утверждений не будет выполнено, вы получите сообщение о том, что-то идет не так, как планируется.

Есть коротка запись для использования expect() : нужно передать количество планируемых утверждений в качестве второго параметра test() или asyncTest() :

// Пользовательская функция function ajax(successCallback) { $.ajax({ url: "server.php", success: successCallback }); } // Сообщаем QUnit, что мы ожидаем выполнения 3 утверждений test("asynchronous test", 3, function() { // Останавливаем тест stop(); ajax(function() { ok(true); }) ajax(function() { ok(true); ok(true); }) setTimeout(function() { start(); }, 2000); })

Заключение

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

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

JavaScript уже не торт!

Наверное, не нужно тебе объяснять, что сегодня JavaScript - это не просто язык для оживления внешнего вида приложения. Но я все равно объясню и сделаю небольшое введение, ведь тогда мне заплатят еще больше денег! 🙂 Так вот, времена, когда JavaScript использовали для шуток или изготовления менюшек, безвозвратно прошли. Теперь это самостоятельный язык, который одинаково хорошо работает как на клиенте, так и на сервере. Роль JavaScript существенно повысилась, а значит, при написании кода нужно не стесняться пользоваться хорошо зарекомендовавшими себя в других языках программирования практиками.

Что я подразумеваю под практиками и парадигмами? Конечно же, архитектурный шаблон MVC (model view controller) и паттерны организации кода. Следуя этим нехитрым премудростям, ты сможешь писать более качественный код, который будет не только легко сопровождаться, но и обладать способностью к автоматическому тестированию.

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

Ни для кого не секрет, что самым популярным способом тестирования всегда была банальная проверка на «глаз». Его суть проста до безобразия - написал пару тысяч строк кода, решил задачу и запускаешь свое творение. Поигрался, покликал - вроде бы все работает, можно заливать на боевой сервер. Все предельно просто, и при должном внимании разработчика (в идеале отдельного человека по прозвищу «тестер») можно положиться на корректность работы приложения.

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

Таким образом, программист получает возможность обнаружить лишь самые грубые ошибки. К сожалению, «тупые» и «непредусмотренные» пользовательские действия, а также хитрые ходы в бизнес логике в 99% случаев остаются за кадром.

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

Как-то раз мне поручили разработать небольшую программку. По функционалу проект напоминал простейшую CRM, которую я и реализовал в кратчайшие сроки. Получив причитающееся вознаграждение, я передал все исходники заказчику и на восемь месяцев забыл о проекте. Дальше началось самое интересное. Заказчик решил серьезно расширить функционал программы и призвал меня на помощь. Естественно, я взялся и начал ваять функцию за функцией... Сначала это было несложно, но, когда дошло дело до общей интеграции функционала, жужжащий рой багов ринулся в мою сторону. Куски кода начали конфликтовать, и приходилось тратить уйму времени на разруливание конфликтов. «Ну а как же ты не видел, что с твоим приложением проблемы?» - спросят читатели. Отвечу: я его запускал, но из-за того, что приложение разрослось, мне банально не хватало времени и нервов протестировать весь функционал скопом. Я ограничивался тестом лишь отдельных функций и щедро поплатился за это. Мораль сей басни: «Думай о тестировании как о неотъемлемой части разработки».

Unit-тесты как серебряная пуля

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

После завершения разработки новой функции (возможен вариант написания тестов и до начала разработки) девелопер пишет специальный код для тестирования своего кода. В нем нужно сымитировать различные ситуации и возвращаемые значения. Например, мы написали функцию для усечения пробелов (trim). Чтобы протестировать ее работоспособность, мы должны подготовить несколько тестов, которые позволят утверждать, что:

  • при передаче строки « строка » на выходе мы получим «строка»;
  • при передаче сроки «строка 9 » на выходе мы получим «строка 9»;

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

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

Тесты!= лишний код

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

Когда на тесты нет времени

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


Насчет сжатых сроков я соглашусь, а вот по части лишнего кода готов поспорить. С одной стороны, да - тесты требуют дополнительного кода, а значит, и времени на его написание. С другой стороны, этот код исполняет роль подушек безопасности в автомобиле и обязательно окупится с ростом приложения.
  • Cristian Johansen «Test-Driven JavaScript Development» (goo.gl/mE6Is) - одна из немногих книг, рассматривающих JavaScript с точки зрения написания тестов.
  • Джон Резинг, Беэр Бибо «Секреты JavaScript ниндзя» (goo.gl/xquDkJ) - хорошая книга, которая пригодится в первую очередь JS-разработчикам со средним уровнем подготовки. В книге детально рассматриваются вопросы написания эффективного кросс-браузерного кода, нюансы обработки событий и много других вкусностей.
  • Дэвид Флэнаган «JavaScript. Полное руководство» (goo.gl/rZjjk) - книга была переиздана шесть раз, и каждый релиз становится бестселлером. Действительно, это самое подробное руководство по JavaScript, которое обязан хотя бы один раз прочитать каждый JS-разработчик.
  • PhantomJS + JSCoverage + QUnit или консольные JS юнит-тесты с подсчетом покрытия (goo.gl/FyQ38) - автор статьи демонстрирует использование связки перечисленных пакетов для сбора статистики и подсчета процента покрытия кода тестами.
  • Полезные примеры использования PhantomJS - на странице представлено большое количество боевого применения PhantomJS.

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

Почему я утверждаю, что задумываться о тестировании нужно до написания основного кода? Да потому, что код, который изначально предполагается покрывать unit-тестами, пишется в несколько другом стиле. Не всякий код можно протестировать. Код, в котором смешивается логика и представления, да еще и распиханный не пойми где, невозможно нормально протестировать. Тут я всегда советую придерживаться нескольких простых правил:

  • Не нужно писать больших функций. Каждая функция должна решать одну проблему, а не 100500 возможных ситуаций. Например, не нужно вешать код отправки данных на сервер в функцию, отвечающую за их подготовку.
  • Функция, состоящая из более десяти строчек кода, скорей всего, плохая функция.
  • Логика и представление ни в коем случае не должны быть вместе.
QUnit - классика жанра от создателей jQuery

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


Загрузить последнюю версию QUnit ты можешь с официального сайта . Библиотека поставляется в виде одного JS и CSS-файла. Предположим, что с загрузкой необходимых компонентов ты разобрался, а раз так, то самое время написать пробный тест. Не будем далеко ходить и попробуем протестировать функцию trim().

Для демонстрации тестов я создал простейший проект со следующей структурой:

  • index.html - основной файл, который будет отображать результаты тестов;
  • qunit-1.12.0.js - файл библиотеки QUnit;
  • example.js - файл, содержащий код для тестирования (в нашем случае описание функции trim());
  • test.js - файл с тестами;
  • qunit-1.12.0.css - стили для оформления отчета с тестами.

Содержимое файла index.html и test.js представлено в листинге 1 и 2. Больше всего нас интересует второй листинг, в котором приведено объявление тестируемой функции (trim()) и код тестов для проверки ее работоспособности. Обрати внимание, сама функция trim() может располагаться где угодно, я ее засунул во второй листинг только ради экономии места в журнале.
Теперь посмотрим на сами тесты. Библиотека QUnit.js предлагает нам ряд методов:

  • test() - обертка для описания теста;
  • ok() - утверждение позволяет проверить истинность первого параметра. В нашем примере я передаю ей вызов определенной нами функции trim() и сравниваю со значением, которое я ожидаю получить. Если условие истинно - тест пройден;
  • equal() - метод позволяет проверить равенство первого и второго параметра. Сразу обрати внимание, что данный метод выполняет нестрогую проверку, поэтому годится только для скалярных величин;
  • notEqual() - противоположен equal(). Выполняется, если первое значение не равно второму;
  • strictEqual() - аналогичен equal() с одним лишь отличием - он использует строгую проверку (то есть проверяет еще и тип данных);
  • notStrictEqual() - метод противоположен strictEqual();
  • deepEqual() - метод для рекурсивных утверждений, применяется для примитивов, массивов, объектов;
  • notDeepEqual() - метод противоположен deepEqual();
  • raises() - утверждение для тестирования функций обратного вызова, генерирующих исключение.

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

Листинг 1. Содержимое файла index.html Тестирование с помощью QUnit

С тестированием простых функций вроде разобрались. Во всяком случае, мне добавить больше нечего. Дальше надо брать реальный код и пробовать писать тесты самостоятельно. Посмотрим на другую часто возникающую перед JavaScript-разработчиками задачу - тестирование асинхронных функций. Приложение, напичканное JavaScript-кодом, в 99% взаимодействует с серверной частью при помощи AJAX. Оставлять этот код без проверки также нельзя, но написание тестов будет выглядеть немного по-другому. Рассмотрим пример:

AsyncTest("myAsyncFunc()", function () { setTimeout(function () { ok(myAsyncFunc() == true, "Данные успешно переданы"); start(); }, 500); });

Главное отличие этого примера от предыдущего - вместо обертки test() применяется asyncTest(), тем самым напрямую заявляя, что меня интересует именно асинхронное тестирование. Дальше я запускаю время ожидания в 500 миллисекунд. За это время функция myAsyncFunc() должна передать данные на тестовый сервер и, если все OK, вернуть true. Вот здесь наступает самый интересный момент. Когда происходит вызов asyncTest(), поток выполнения останавливается, и по окончании теста его необходимо самостоятельно запустить. Для управления потоком выполнения в QUnit есть методы start() и stop().


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

AsyncTest("myAsyncFunc()", function () { expect(3); // Здесь делаем три проверки ok(myAsyncFunc(), "Делаем мир лучше 1"); ok(myAsyncFunc(), "Делаем мир лучше 2"); ok(myAsyncFunc(), "Делаем мир лучше 3"); setTimeout(function () { start(); }, 3000); });

Тест для пользовательских действий

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

Листинг 3. Логирование нажатых клавиш function KeyLogger(target) { if (!(this instanceof KeyLogger)) { return new KeyLogger(target); } this.target = target; this.log = ; var self = this; this.target.off("keydown").on("keydown", function(event) { self.log.push(event.keyCode); }); }

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

Листинг 4. Код теста для KeyLogger test("Тест записи клавиш", function () { var event, $doc = $(document), keys = KeyLogger($doc); event = $.Event("keydown"); event.keyCode = 9; $doc.trigger(event); equal(keys.log.length, 1, "Клавиша записана"); equal(keys.log, 9, "Записано нажатие клавиши с кодом 9"); });

В самом начале листинга с тестом я подготавливаю событие для эмуляции нажатия клавиши - «keydown». Нас будет интересовать нажатие клавиши Tab (код 9). Затем при помощи метода trigger() я отправляю приготовленное событие, после чего можно приступать к тестированию. Сначала проверяем общую картину - была ли нажата клавиша, а затем ее код.

DOM под прикрытием тестов

Раз Qunit.js позволяет тестировать пользовательские действия, то с написанием тестов для DOM тоже не должно быть проблем. Это действительно так, и приведенный ниже пример подтвердит мои слова. Я не буду его комментировать, просто взгляни на код, и все станет понятным:

Test("Добавляем новый элемент div", function () { var $fixture = $("#qunit-fixture"); $fixture.append("Это новый див"); equal($("div", $fixture).length, 1, "Новый div успешно добавлен!"); });

PhantomJS - запускаем тесты из консоли

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

Достаточно элегантно эту проблему позволяет решить проект PhantomJS . Это не очередной фреймворк для написания unit-тестов, а полноценная консольная версия движка WebKit. Если сказать проще, то это приложение эмулирует браузер. При помощи PhantomJS реально не просто автоматизировать проверку выполнения тестов, а еще и решить множество задач, рано или поздно возникающих перед разработчиком: получение результатов рендеринга страниц в файл (PNG, JPG), функции сетевого монитора (скорость загрузки, общая производительность и прочее), эмуляция действий пользователя и так далее. Рекомендую не полениться и почитать официальную документацию по этому проекту, обязательно найдешь что-то интересное для себя.

PhantomJS можно собрать под разные платформы (*nix, OS X, Windows). Если ты все разрабатываешь под Windows, то нет никаких проблем - сливай бинарники, и вперед. Небольшие трудности с запуском могут возникнуть, если у тебя установлено два видеоадаптера, один из которых NVIDIA. В этом случае тебе придется воспользоваться хаком, описанным во врезке.


Попробуем познакомиться с PhantomJS на практике. Чтобы пропустить через PhantomJS тесты, подготовленные в прошлом разделе, и получить результаты выполнения в консоль, нам потребуется специальный сценарий-лоадер - run-qunit.js . Открываем консоль (я работаю в Windows, поэтому использую cmd) и набиваем команду в формате

phantom.exe

В моем случае команда запуска получилась такой:

E:\soft\phantomjs>phantomjs.exe E:\temp\testjsforx\qunit\run-qunit.js file:///E: /temp/testjsforx/qunit/index.html

Результат ее выполнения:

Tests completed in 2592 milliseconds. 9 assertions of 9 passed, 0 failed.

All tests passed

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

Проблемы PhantomJS в Windows

Так уж получилось, но все примеры к этой статье я тестировал не в Linux, а под старой доброй Windows 7. Оказывается, у PhantomJS есть небольшие проблемы при работе на системах, в которых используется несколько видеоадаптеров. На моем ноутбуке помимо интегрированного видеочипа еще тусуется NVIDIA, и из-за этого PhantomJS категорически отказывался реагировать на команду phantom.exit(). В результате после выполнения сценария процесс PhantomJS не завершал свою работу и продолжал висеть в памяти. Окно терминала также переставало реагировать на команды завершения ( не помогал).

Если ты столкнулся с подобной проблемой и планируешь использовать PhantomJS на Windows, то приготовься проделать следующий хак. Открой панель управления NVIDIA. Найди в дереве пункт «Параметры 3D». С правой стороны должна появиться опция «Предпочтительный графический адаптер». По умолчани. ее значение установлено в «Автовыбор». Нам надо ее поменять на «Высокопроизводительный процессор NVIDIA» или «Интегрированное графическое оборудование». После этого нехитрого трюка PhantomJS начал вести себя послушно.

На примере простого приложения-калькулятора на Node.js. Тестировать будем с помощью фреймворка Mocha.

Что должно уметь наше приложение:

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

Что нам потребуется:

  • Node.js и npm;
  • Знание JavaScript: синтаксис и структура кода , типы данных , математические операции и условные выражения .

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

Настраиваем среду

Так как мы используем Node.js, нужно создать локальное окружение для файлов и зависимостей.

Создайте новую папку calc . В командной строке перейдите в эту директорию и создайте новый проект командой npm init , которая создаст новый файл package.json для нашей программы.

Вам предложат ввести имя пакета, версию, описание и прочую информацию о пакете. Вы можете ввести имя calc.js и дальше жать Enter для присвоения значений по умолчанию. Когда вы дойдёте до test command , введите mocha - это фреймворк для тестирования, который мы будем использовать:

test command: mocha

После ввода всей информации скрипт создаст файл package.json , который выглядит примерно так:

{ "name": "calc.js", "version": "1.0.0", "description": "Простой калькулятор на Node.js", "main": "index.js", "scripts": { "test": "mocha" }, "author": "", "license": "ISC" }

Последний шаг на данном этапе - установка Mocha. Введите следующую команду для установки:

Npm install --save-dev mocha

После применения этой команды появится папка node_modules , файл package-lock.json , а в файле package.json появятся следующие строки:

"devDependencies": { "mocha": "^4.0.1" }

Создайте файл test.js . Мы воспользуемся встроенным в Node.js модулем assert , чтобы проверить верность равенства true и true . Так как оно верно, тест должен пройти успешно:

Const assert = require("assert"); it("должно возвращать true", () => { assert.equal(true, true); });

Теперь запустите тест из командной строки:

$ npm test > mocha ✓ должно возвращать true 1 passing (8ms)

Тест прошёл как и ожидалось, поэтому с настройкой среды покончено. Удалите из test.js всё, кроме строки const assert = require("assert"); .

Мы будем использовать файл test.js на протяжении всего процесса создания приложения. Создайте ещё два файла: operations.js для арифметических и валидационных функций и calc.js для самого приложения. Мы используем так много файлов, чтобы они не становились слишком длинными и сложными. Вот наш текущий список файлов:

  • calc.js ;
  • node_modules ;
  • operations.js ;
  • package-lock.json ;
  • package.json ;
  • test.js ;

Давайте добавим первый настоящий тест для нашего приложения.

Добавляем математические операции

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

Начнём со сложения. Мы напишем тест, в котором однозначно получится ожидаемая сумма двух чисел. В коде ниже мы проверяем, равняется ли сумма 1 и 3 с помощью функции add() 4:

Const assert = require("assert"); it("правильно находит сумму 1 и 3", () => { assert.equal(add(1, 3), 4); });

После запуска теста с помощью команды npm test мы видим следующее:

> mocha 0 passing (9ms) 1 failing 1) правильно находит сумму 1 и 3: ReferenceError: add is not defined at Context.it (test.js:5:16) npm ERR! Test failed. See above for more details.

Тест провалился с сообщением ReferenceError: add is not defined . Мы тестируем функцию add() , которой ещё нет, поэтому такой результат вполне ожидаем.

Создадим функцию add() в файле operations.js :

Const add = (x, y) => (+x) + (+y);

Эта функция принимает два аргумента x и y и возвращает их сумму. Вы могли заметить, что мы пишем (+x) + (+y) , а не x + y . Мы используем унарный оператор для приведения аргумента к числу, на случай, если ввод будет строкой.

Примечание Здесь используется добавленная в ES6 стрелочная функция и неявный возврат.

Так как мы используем Node.js и разбиваем код на множество файлов, нужно воспользоваться module.exports , чтобы экспортировать код:

Const add = (x, y) => (+x) + (+y); module.exports = { add }

В начале файла test.js мы импортируем код из operations.js с помощью require() . Так как мы используем функцию через переменную operations , нужно поменять add() на operations.add() :

Const operations = require("./operations.js"); const assert = require("assert"); it("правильно находит сумму 1 и 3", () => { assert.equal(operations.add(1, 3), 4); });

Запускаем тест:

$ npm test > mocha ✓ правильно находит сумму 1 и 3 1 passing (8ms)

Теперь у нас есть работающая функция, и тесты проходят успешно. Так как функции других операций работают схожим образом, добавить тесты для subtract() , multiply() и divide() не составит труда:

It("правильно находит сумму 1 и 3", () => { assert.equal(operations.add(1, 3), 4); }); it("правильно находит сумму -1 и -1", () => { assert.equal(operations.add(-1, -1), -2); }); it("правильно находит разность 33 и 3", () => { assert.equal(operations.subtract(33, 3), 30); }); it("правильно находит произведение 12 и 12", () => { assert.equal(operations.multiply(12, 12), 144); }); it("правильно находит частное 10 и 2", () => { assert.equal(operations.divide(10, 2), 5); });

Теперь создадим и экспортируем все функции в test.js :

Const add = (x, y) => (+x) + (+y); const subtract = (x, y) => (+x) - (+y); const multiply = (x, y) => (+x) * (+y); const divide = (x, y) => (+x) / (+y); module.exports = { add, subtract, multiply, divide, }

И запустим новые тесты:

$ npm test > mocha ✓ правильно находит сумму 1 и 3 ✓ правильно находит сумму -1 и -1 ✓ правильно находит разность 33 и 3 ✓ правильно находит произведение 12 и 12 ✓ правильно находит частное 10 и 2 5 passing (8ms)

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

Добавляем валидацию

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

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

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

  • Оба ввода - числа.
  • Один ввод - число, а другой - строка.
  • Оба ввода - строки.
  • it("сообщает об ошибке при использовании строки вместо числа", () => { assert.equal(operations.validateNumbers("sammy", 5), false); }); it("сообщает об ошибке при использовании двух строк вместо чисел", () => { assert.equal(operations.validateNumbers("sammy", "sammy"), false); }); it("успех при использовании двух чисел", () => { assert.equal(operations.validateNumbers(5, 5), true); });

    Функция validateNumbers() будет проверять оба параметра. Функция isNaN() проверяет, не является ли параметр числом, и если нет, то возвращает false . В противном случае она возвращает true , что означает успешную валидацию.

    Const validateNumbers = (x, y) => { if (isNaN(x) && isNaN(y)) { return false; } return true; }

    Не забудьте добавить validateNumbers в module.exports в конце файла. Теперь можно запускать новые тесты:

    $ npm test 1) сообщает об ошибке при использовании строки вместо числа ✓ сообщает об ошибке при использовании двух строк вместо чисел ✓ успех при использовании двух чисел 7 passing (12ms) 1 failing 1) сообщает об ошибке при использовании строки вместо числа: AssertionError : true == false + expected - actual -true +false

    Два теста прошли, но один провалился. Проверка на ввод двух чисел прошла успешно, так же как и проверка на ввод двух строк. Чего нельзя сказать о проверке на ввод строки и числа.

    Если взглянуть на нашу функцию ещё раз, то можно заметить, что оба параметра должны быть NaN , чтобы функция вернула false . Если мы хотим добиться того же эффекта, когда хотя бы один из параметров равен NaN , нужно заменить && на || :

    Const validateNumbers = (x, y) => { if (isNaN(x) || isNaN(y)) { return false; } return true; }

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

    ✓ сообщает об ошибке при использовании строки вместо числа ✓ сообщает об ошибке при использовании двух строк вместо чисел ✓ успех при использовании двух чисел 8 passing (9ms)

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

    Создаём интерфейс

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

    На данный момент файл calc.js должен быть пуст. Здесь и будет храниться наше приложение. Сначала нужно импортировать функции из operations.js :

    Const operations = require("./operations.js");

    Сам интерфейс будет использовать встроенный в Node.js CLI-модуль Readline :

    Const readline = require("readline");

    После импортирования всего, что нужно, можно приступить к созданию приложения. Для создания интерфейса мы будем использовать readline , доступный через переменную rl:

    Const rl = readline.createInterface({ input: process.stdin, output: process.stdout });

    Первое, что пользователь должен видеть после запуска программы, - приветственное сообщение и инструкции по использованию. Для этого мы воспользуемся console.log() :

    Console.log(` Calc.js Вы открыли калькулятор на Node.js! Версия: 1.0.0. Использование: пользователь должен ввести два числа, а затем выбрать, что с ними сделать. `);

    Прежде чем мы займёмся самими функциями калькулятора, давайте проверим, что console.log() работает как надо. Мы сделаем так, чтобы программа выводила сообщение и завершала работу. Для этого добавьте в конце вызов метода rl.close() .

    Чтобы запустить приложение, введите node и имя файла:

    $ node calc.js Calc.js Вы открыли калькулятор на Node.js! Версия: 1.0.0. Использование: пользователь должен ввести два числа, а затем выбрать, что с ними сделать.

    Программа выводит приветственное сообщение и завершает свою работу. Теперь нужно добавить пользовательский ввод. От пользователя требуется следующее: выбрать два числа и одну операцию. Каждый ввод будет запрашиваться методом rl.question() :

    Rl.question("Введите первое число: ", (x) => { rl.question("Введите второе число: ", (y) => { rl.question(` Выберите одну из следующих операций: Сложение (+) Вычитание (-) Умножение (*) Деление (/) Ваш выбор: `, (choice) => { // здесь ещё появится код rl.close(); }); }); });

    Переменной x присваивается первое число, y - второе, а choice - выбранная операция. Теперь наша программа запрашивает ввод, но ничего не делает с полученными данными.

    После третьего ввода нужно проверить, что были введены только числа. Для этого воспользуемся функцией validateNumbers() . С помощью оператора НЕ мы проверим, были ли введены числа, и, если это не так, завершим работу программы:

    If (!operations.validateNumbers(x, y)) { console.log("Можно вводить только числа! Пожалуйста, перезапустите программу."); }

    Если всё введено верно, то теперь нужно запустить соответствующий операции метод, созданный ранее. Для обработки четырёх возможных вариантов выбора мы воспользуемся выражением switch и выведем результат операции. Если была выбрана несуществующая операция, будет выполнен блок default , сообщающий пользователю о необходимости повторить попытку:

    If (!operations.validateNumbers(x, y)) { console.log("Можно вводить только числа! Пожалуйста, перезапустите программу."); } else { switch (choice) { case "1": console.log(`Сумма ${x} и ${y} равна ${operations.add(x, y)}.`); break; case "2": console.log(`Разность ${x} и ${y} равна ${operations.subtract(x, y)}.`); break; case "3": console.log(`Произведение ${x} и ${y} равно ${operations.multiply(x, y)}.`); break; case "4": console.log(`Частное ${x} и ${y} равно ${operations.divide(x, y)}.`); break; default: console.log("Пожалуйста, перезапустите программу и выберите число от 1 до 4."); break; } }

    Примечание Здесь в функциях console.log() используются шаблонные строки , допускающие использование выражений.

    /** * Простой калькулятор на Node.js, который использует calculator app that uses * встроенный интерфейс командной строки Readline. */ const operations = require("./operations.js"); const readline = require("readline"); // Используем readline для создания интерфейса const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); console.log(` Calc.js Вы открыли калькулятор на Node.js! Версия: 1.0.0. Использование: пользователь должен ввести два числа, а затем выбрать, что с ними сделать. `); rl.question("Введите первое число: ", (x) => { rl.question("Введите второе число: ", (y) => { rl.question(` Выберите одну из следующих операций: Сложение (+) Вычитание (-) Умножение (*) Деление (/) Ваш выбор: `, (choice) => { if (!operations.validateNumbers(x, y)) { console.log("Можно вводить только числа! Пожалуйста, перезапустите программу."); } else { switch (choice) { case "1": console.log(`Сумма ${x} и ${y} равна ${operations.add(x, y)}.`); break; case "2": console.log(`Разность ${x} и ${y} равна ${operations.subtract(x, y)}.`); break; case "3": console.log(`Произведение ${x} и ${y} равно ${operations.multiply(x, y)}.`); break; case "4": console.log(`Частное ${x} и ${y} равно ${operations.divide(x, y)}.`); break; default: console.log("Пожалуйста, перезапустите программу и выберите число от 1 до 4."); break; } } rl.close(); }); }); });

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

    $ node calc.js Введите первое число: 999 Введите второе число: 1 Ваш выбор: 2 Разность 999 и 1 равна 998.

    Программа успешно завершила свою работу, выведя правильный результат. Поздравляем, вы написали простой калькулятор с помощью Node.js и изучили основы TDD-разработки.

    Javascript - очень популярный язык для клиентской веб-разработки. Язык JavaScript является динамическим языком и базируется на прототипировании. Не смотря на название, он не относится к языку Java, хотя и использует схожий C-подобный синтаксис, схожие конвенции именования.

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

    Целевая аудитория теста по JavaScript

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

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

    Предварительные требования к тесту javascript

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

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

    Структура теста по JavaScript

    Вопросы теста покрывают следующие темы:

    • основные языковые конструкции JavaScript (объявление переменных, функций и т.д.);
    • операторы (ветвление, циклы);
    • работу со строками в JavaScript;
    • работу с массивами в JavaScript;
    • связку "JavaScript - HTML" (теги, используемые для интеграции)
    • другие понятия, которые не вошли в обозначенные выше темы.
    Дальнейшее развитие теста по JavaScript

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

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

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

    Со временем проект наполняется новыми функциональными возможностями, что удлиняет и усложняет процесс проверки его работы. Для автоматизации используются модульное (unit) тестирование.

    Существуют 2 подхода к построению тестовых сценариев:

    • Whitebox Testing – написание тестов основывается на реализации функционала. Т.е. на мы проверяем по тем же алгоритмам, на которых строиться работы модулей нашей системы. Такой подход не гарантирует корректность работы системы в целом.
    • Blackbox Testing – создание сценариев базируется на спецификациях и требованиях к системе. Так можно проверить правильность результатов работы всего приложения, однако подобный подход не позволяет отловить мелкие и редкие ошибки.
    Что тестировать

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

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

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

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

    2) Снижают время на отладку

    3) Позволяют тестировать часто изменяемый код.

    Из 3х основных компонент фронтэнда (HTML, CSS, JavaScript) тестировать нужно, пожалуй лишь JavaScript-код. CSS проверяется исключительно визуальным методом, когда разработчик/тестировщик/заказчик просматривает графический интерфейс в различных браузерах. HTML – разметка проверяется тем же методом.

    Как тестировать

    При построении сценариев проведения тестов стоит руководствоваться следующими принципами:

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

    Для unit-тестирования js-кода существуют несколько библиотек. Пожалуй самой распространённой является QUnit. Для проведения модульных тестов с помощью этой библиотеки нам потребуется создать «песочницу» — простую html-страницу, в которой будут подключена библиотека для тестирования, код, которой нужно подвергнуть тестам, и собственно сами тесты.

    Функции для тестов:

    (function() { window.stepen = function(int) { var result = 2; for (var i = 1; i< int; i ++) { result = result * 2; } return result; } window.returnFunc = function() { return "ok"; } })();

    Листинг тестов:

    Test("stepen()", function() { equal(stepen(2), 4, "2^2 - equal method"); ok(stepen(3) === 8, "2^3 - ok method"); deepEqual(stepen(5), 32, "2^5 - deepEqual method"); }); asyncTest("returnFunc()", function() { setTimeout(function() { equal(returnFunc(), "ok", "Async Func Test"); start(); }, 1000); });

    Как видно, QUnit поддерживает 3 функции для сравнения результатов выполнения кода с ожидаемым:

    • ok() – считает тест успешным, если возвращаемый результат = true
    • equal() – сравнивает результат с ожидаемым
    • deepEqual() – сравнивает результат с ожидаемым, проверяя его тип

    Результат выполнения:

    Как видно, библиотека QUnit проводит тестирование кода сразу для нескольких браузеров.

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

    Важно помнить

    Особенностью современного js-кода является асинхронность его выполнения. Библиотеки для тестирования как правило имеют возможность проведения асинхронных тестов. Но к примеру если вы пытаетесь протестировать функцию, которая, скажем, посылает get-запрос на бэкэнд и возвращает ответ от него, то для проведения тестов придется останавливать поток функцией stop(), запускать тестируемую функцию, а затем заново запускать поток методом start(), «обернув его» в setTimeout(). Т.е. вы должны заложить какой-то промежуток времени, в течении которого должно завершиться выполнение функции. Нужно тщательно выбирает длительность этого отрезка, .к. с одной стороны долгая работа метода может быть как особенность или даже необходимостью конкретной реализации функционала приложения, так и некорректным поведением.

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

    Для примера тестирования приложений, написанных с использованием Backbone.js воспользуемся проектом, описанным в .

    Модульными тестами можно проверить:

    • Корректность создания моделей и контроллеров
    • Правильность данных в моделях
    • Исполнение методов контроллеров (для этого они должны возвращать результат)
    • Успешность загрузки представлений

    Код тестов:

    Test("Backbone.js", function() { ok(sample, "Namespace check"); ok(sample.routers.app, "Router check"); ok(sample.core.pageManager.open("chat"), "Page opening test (Controller method call)") ok(sample.core.state, "Model check"); equal(sample.core.state.get("content"), "sintel", "Model data get test"); stop(); ok(function() { $.ajax({ url: "app/templates/about.tpl", dataType: "text" }).done(function(data) { self.$el.html(data); return data; }) }, "Template loading check"); setTimeout(function() { start(); }, 1000); });

    Результат работы с ошибками тестирования:

    Автоматизация запуска тестов

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

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

    /** * Wait until the test condition is true or a timeout occurs. * Useful for waiting * on a server response or for a ui change (fadeIn, etc.) to occur. * * @param testFx javascript condition that evaluates to a boolean, * it can be passed in as a string (e.g.: "1 == 1" or * "$("#bar").is(":visible")" or * as a callback function. * @param onReady what to do when testFx condition is fulfilled, * it can be passed in as a string (e.g.: "1 == 1" or * "$("#bar").is(":visible")" or * as a callback function. * @param timeOutMillis the max amount of time to wait. If not * specified, 3 sec is used. */ function waitFor(testFx, onReady, timeOutMillis) { var maxtimeOutMillis = timeOutMillis ? timeOutMillis: 3001, //< Default Max Timout is 3s start = new Date().getTime(), condition = false, interval = setInterval(function() { if ((new Date().getTime() - start < maxtimeOutMillis) && !condition) { // If not time-out yet and condition not yet fulfilled condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code } else { if(!condition) { // If condition still not fulfilled // (timeout but condition is "false") console.log(""waitFor()" timeout"); phantom.exit(1); } else { // Condition fulfilled (timeout and/or condition is //"true") console.log(""waitFor()" finished in " + (new Date().getTime() - start) + "ms."); typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it"s supposed to do once the // condition is fulfilled clearInterval(interval); //< Stop this interval } } }, 100); // repeat check every 250ms }; }; if (phantom.args.length === 0 || phantom.args.length > 2) console.log("Usage: run-qunit.js URL"); phantom.exit(); } var page = new WebPage(); // Route "console.log()" calls from within the Page // context to the main Phantom context (i.e. current "this") page.onConsoleMessage = function(msg) { console.log(msg); }; page.open(phantom.args, function(status){ if (status !== "success") { console.log("Unable to access network"); phantom.exit(); } else { waitFor(function(){ return page.evaluate(function(){ var el = document.getElementById("qunit-testresult"); if (el && el.innerText.match("completed")) { return true; } return false; }); }, function(){ var failedNum = page.evaluate(function(){ var el = document.getElementById("qunit-testresult"); console.log(el.innerText); try { return document.getElementsByClassName("fail"). innerHTML.length; } catch (e) { return 0; } return 10000; }); phantom.exit((parseInt(failedNum, 10) > 0) ? 1: 0); }); } });

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