Тест на js со случайными вопросами. Тестирование JavaScript

Тестирование - неотъемлемая часть цикла разработки программного обеспечения. Начинающие команды девелоперов зачастую недооценивают его роль и проверяют работоспособность приложения по старинке - «работает, да и ладно». Рано или поздно эта стратегия дает сбой и баг-трекер начинает захлестывать бесчисленная армия тасков. Чтобы не угодить в подобную западню, рекомендую раз и навсегда разобраться с нюансами тестирования 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 <путь к run-qunit.js> <путь к странице с тестами>

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

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 начал вести себя послушно.

Однажды мой друг выразил своё недоумение по поводу того, как вообще язык 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). Дублёры бывают следующих видов:

  • Fake;
  • Dummy.

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 . Проверка того, что все пожелания заказчика или бизнес-аналитика () выполнены путем проверки результатов запуска тестов.

При написании тестов в стиле 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 тестированием, поскольку они прекрасно дополняют друг друга.

Теперь на сайте доступно тестирование на знание следующих тем: HTML , CSS , JavaScript , PHP , SQL .

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

Безусловно, все тесты бесплатные и пройти их может любой желающий.

Порядок прохождения теста:

  1. Переходите по ссылке "Начать тестирование " у соответствующего теста.
  2. Отвечаете на поставленные вопросы, выбрав единственный правильный вариант.
  3. По завершению тестирования Вы увидите свой балл , количество ошибок , а также разбор каждого вопроса из теста.

Внимание! Вернуться к предыдущему вопросу не получится, поэтому прежде, чем отвечать, думайте.

Доступные на данный момент тесты

  1. HTML

    • Всего тест прошло: 75424 человека
    • Средний балл: 2.83 из 5 баллов.

    Тест на знание основ HTML . От Вас потребуется знание основных HTML-тегов , а также грамотное их использование. Так же необходимо понимание особенностей стандарта XHTML 1.1 .

  2. CSS

    • Всего тест прошло: 32828 человек
    • Средний балл: 3.37 из 5 баллов.

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

  3. JavaScript

    • Всего тест прошло: 24845 человек
    • Средний балл: 3.31 из 5 баллов.

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

  4. PHP

    • Всего тест прошло: 33239 человек
    • Средний балл: 3.03 из 5 баллов.

    Данный тест проверяет Ваши знания по языку PHP. От Вас требуется знание основных конструкций PHP, работы с переменными, сессий, реализации редиректа и прочих стандартных вещей.
    Убедительная просьба: В тесте содержится много вопросов по типу: "Что выведет скрипт?". Большая просьба, не надо копировать его и проверять. Будьте честны перед самими собой.

  5. SQL

    • Всего тест прошло: 18014 человек
    • Средний балл: 3.28 из 5 баллов.

    Данный тест проверяет Ваши знания по языку запросов SQL. Вопросы затрагивают только самые базовые знания этого языка, без какого-либо углубления. От Вас потребуется знание самых основных SQL-запросов, а также грамотное их использование.

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

JavaScript уже не тот

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

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

Ошибка большинства тестеров

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Не всякий код тестируется

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

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

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

    Загрузить последнюю версию QUnit ты можешь с официального сайта - http://qunitjs.com/ . Библиотека поставляется в виде одного 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

    Листинг 2. Файлы тестов и функция trim()

    function trim(string) { return (string || "").replace(/^\s+|\s+$/g, ""); } test("Тест функции trim()", function() { ok(trim(" test ") == "test", "обрезаем крайние пробелы"); ok(trim(" 1 ") == "1", "очень много пробелов по бокам"); ok(trim(" 24 ") == "24", "пробелы и табы по бокам"); equal(trim(""), "", "Пустая строка"); strictEqual(trim(" ][акер") });

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

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

    Главное отличие этого примера от предыдущего – вместо обертки test() применяется asyncTest(), тем самым напрямую заявляя, что меня интересует тестирование именно асинхронное тестирование. Дальше я запускаю время ожидание в 500 мл. сек. За это время функция myAsyncFunc() должна передать данные на тестовый сервер, и если все ништяк вернуть 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[ 0 ], 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 успешно добавлен!"); });

    Phantom.JS – запускаем тесты из консоли

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

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

    Phantom.js можно собрать под разные платформы (nix, mac OS X, windows). Если ты все разрабатываешь под Windows, то нет никаких проблем – сливай бинарники и вперед. Небольшие проблемы с запуском могут возникнуть, если у тебя установлено два видео адаптера, один из которых NVidia. В этом случае тебе придется воспользоваться хаком, описанном во врезке.

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

    Phantom.exe <путь к run-qunit.js> <путь к странице с тестами>

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

    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

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

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

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

    Правила хороших тестов

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

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

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

  • Cristian Johansen «Test-Driven JavaScript Development» – одна из немногих книг, рассматривающие JavaScript с точки зрения написания тестов;
  • Джон Резинг, Беэр Бибо «Секреты JavaScript ниндзя» – хорошая книга, которая пригодится в первую очередь JS разработчикам со средним уровнем подготовки. В книге детально рассматриваются вопросы написания эффективного кросс-браузерного кода, нюансы обработки событий и много других вкусностей.