7 октября 2010 в 09:23

Юнит-тестирование и CodeCoverage для Javascript-кода

В этой заметке расскажу о своем опыте юнит-тестирования JS-кода, опыте использования среды выполнения тестов js-test-driver, ее возможности code coverage и скручивании ежа с ужом, а именно данных о code coverage от js-test-driver и генератора отчетов о покрытии PHP_CodeCоverage. Расскажу и покажу как получить вот такие отчеты о покрытии кода...

Итак, потребовалось реализовать юнит-тестирование для JS-кода. В качестве среды для выполнения и фреймворка для написания тестов был выбран js-test-driver. Причины таковы:
  • есть в виде плагина для применяемой командой IDE — PhpStorm (к сожалению в настоящий момент плагин не работает на текущей платформе PhpStorm, о чем есть соответствующий тикет в статусе Started)
  • умеет выполнять тесты автоматически в нескольких браузерах
  • работает из командной строки, просто встроить в CI
  • умеет давать отчеты о code coverage

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

Попробуем в деле


Код, который будем тестировать

var greeter = function(toSay){
  this.whatToSay = toSay;
}

greeter.prototype.say = function(sayBye){
  if(sayBye == true)
    return "Goodbye " + this.whatToSay;
  else
    return "Hello " + this.whatToSay;
}

И тест

var testCase = new TestCase("Say");

testCase.prototype.testCase1 = function(){
  var i = new greeter('test');
  assertEquals("Hello test", i.say(false));
};

Файловая структура

  \jstd
    \plugins
      coverage.jar
    code.js
    test.js
    conf
    jstestdriver.jar

Конфигурация для запуска (файл conf в формате YAML)

load:
 - code.js
 - test.js

server: http://localhost:4224


Запускаем


Сперва стартуем сервер

H:\jstd>java -jar jstestdriver.jar --port 4224

Запускаем браузер, идем на localhost:4224, овладеваем браузером. Запускаем прогон тестов.
UPD: подключить к тестированию можно произвольное количество произвольных браузеров. Можно даже подключиться с удаленной машины из-под другой ОС. Подключение браузера к тестированию == открытие страницы на неком сервере (который в данном случае — вам сервер js-test-driver)


H:\jstd>java -jar JsTestDriver.jar --config conf --tests all
..
Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (1,00 ms)
  Chrome 6.0.472.63 Windows: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (1,00 ms)
  Safari 525.28.1 Windows: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (1,00 ms)

Видим, что прогнали всего 2 теста. В т.ч. в браузере Chrome — 1, и он прошел успешно, в браузере Safari — также 1 и также успешно. Все замечательно.

А что там было насчет code coverage?


CodeCoverage подключается отдельным плагином. Данные о покрытии могут либо отображаться в виде статической информации (файл такой-то покрыт на N%) по окончании исполнения тестов, либо могут выгружаться в файл формата LCOV. Авторы предлагают генерировать визуальные отчеты с помощью тулзы genhtml. Беглый поиск портированных под Win32 результатов не дал, поднимать Cygwin или отдельную машину для построения отчетов не хочется...

Запустим тесты с code coverage


Подключим плагин. Отредактируем конфигурационный файл (conf).

load:
 - code.js
 - test.js

server: http://localhost:4224

plugin:
 - name: "coverage"
   jar: "plugins/coverage.jar"
   module: "com.google.jstestdriver.coverage.CoverageModule"

Запустим тесты

H:\jstd>java -jar JsTestDriver.jar --config conf --tests all
Safari: Runner reset.
.Safari: Runner reset.
Chrome: Runner reset.
.Chrome: Runner reset.

Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (1,00 ms)
  Chrome 6.0.472.63 Windows: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (1,00 ms)
  Safari 525.28.1 Windows: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (0,00 ms)

H:/jstd/code.js: 83.33333% covered
H:/jstd/test.js: 100.0% covered

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

H:\jstd>java -jar JsTestDriver.jar --config conf --tests all --testOutput ./out
Safari: Runner reset.
.Safari: Runner reset.
Chrome: Runner reset.
.Chrome: Runner reset.

Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (1,00 ms)
  Chrome 6.0.472.63 Windows: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (1,00 ms)
  Safari 525.28.1 Windows: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (1,00 ms)

Теперь информации о покрытии не видно совсем, но в папке ./out появился файл с покрытием в формате LCOV.

Формат LCOV


Формат lcov-файла, генерируемого js-test-driver предельно прост.

SF:H:/jstd/code.js
DA:1,2
DA:2,2
DA:5,2
DA:6,2
DA:7,0
DA:9,2
end_of_record
SF:H:/jstd/test.js
DA:1,2
DA:3,2
DA:4,2
DA:5,2
end_of_record

SF — файл, для которого приводятся данне далее, DA — данные о покрытии (DA: Строка, СколькоРазВыполнена).

Генерируем красивый отчет: PHP_CodeCoverage


PHPUnit — фреймворк дле реализации юнит-тестирования для PHP, имеет возможность генерировать отчеты о code coverage. Модуль, занимающийся CodeCoverage, очень легко отделяем и очень аккуратно реализован. В состав входит интерфейс PHP_CodeCoverage_Driver, классы, имплементирующие его, могут служить источником данных о Code coverage для прочих компонентов проекта (построитель отчетов в т.ч.).

Xdebug. Как он отдает данные о покрытии?


Для файла...

<?php
xdebug_start_code_coverage();

function a() {
    $x = 10;
}

$b = 30;

var_dump(xdebug_get_code_coverage());

Получим результат...

array(
  'Z:\home\test\www\test.php' =>
    array(
      4 => 1
      8 => 1
      10 => 1
    )
);

Видно что форматы очень похожи, можно сделать свой драйвер
Ниже — простой пример кода, генерирующего отчет о покрытии. Предполагается, что данные о покрытии находятся в файле coverage.dat. Отчет будет расположен в папке CodeCoverageReport.

<?php

include('PHP/CodeCoverage.php');
include('PHP/CodeCoverage/Driver/Lcov.php');
include('PHP/CodeCoverage/Report/HTML.php');

// ./lcov_coverage.dat contains ine coverage report in LCOV format

$coverage = new PHP_CodeCoverage(new PHP_CodeCoverage_Driver_Lcov('./coverage.dat'));

$coverage->start('mytest');
$coverage->stop();

$writer = new PHP_CodeCoverage_Report_HTML();
$writer->process($coverage, 'CodeCoverageReport');

Что получается, можно посмотреть здесь. Можно посмотреть на отчет и увидеть, что наш сложный пример не полностью покрыт тестами, пропущена одна ветка и ее надо срочно покрыть тестами.
Olegas @Olegas
карма
85,2
рейтинг 0,0
Самое читаемое Разработка

Комментарии (29)

  • +1
    Интересно, я сейчас как раз пытаюсь прикрутить кросс-браузерное тестирование Ext JS приложения на базе Selenium, и пока безуспешно. Попробую js-test-driver.
    • 0
      js-test-driver это именно среда запуска юнит-тестов. Не вполне ясно как его скрутить с тем же Selenium'ом.
      • 0
        я вместо селениума хочу попробовать
        • +1
          Пока они не сделают поддержку асинхронных тестов (сейчас ее нет в продакшене) судя по всему будет не легко…
          • +1
            хм, они тут пишут code.google.com/p/js-test-driver/wiki/AsyncTestCase что в 1.2.2 есть асинхронный API.

            и скачивать дают как раз 1.2.2
            • 0
              Хм… когда я его пробовал он там не работал как надо. Может быть с тех пор обновили билд, надо проверить.
            • 0
              Собственно они как-раз отвечают, что асинхронный API есть, но выложена версия без каких-то критикал-багфиксов

              «We've fixed several critical bugs since then. I'll let Cory know we should release an updated version.»
              • 0
                да, я видел. но всё-же есть. нужно дружно просить Кори выпустить новый релиз
              • 0
                да, таки не работает

                Object [object Object] has no method 'defer'

                это они зря конечно
                • +1
                  но версия с svn работает
                  • 0
                    Из SVN взял бинарник? Или сам собрал из сорцов?
  • 0
    А кроме сафари и хрома, он умеет другие движки?

    И есть ли возможность отлавливать ошибки работы с дом-моделью страницы?
    • +1
      Ему без разницы где гонять тесты. Какой браузер натравите на сервер и сделаете сapture — тот и будет гонять тесты. Можно хоть все имеющиеся подключить, и еще с других ОС удаленно тоже подключиться и на них тоже гонять.

      Ошибка работы с дом моделью можете отлавливать через обычные assert*. document, window и любые производные от них (естественно) доступны в коде тестов.

      + он умеет декларативное добавление HTML на страницу прямо во время теста.
      • 0
        Мммм… То есть я правильно понимаю что надо запустить сервер, зайти последовательно на него всеми браузерами, выключить сервер и получить статистику по ошибкам?
        • +3
          Похоже вы невнимательно читали статью…

          1. Запускаем сервер
          2. Подключаем к нему все браузеры
          3. Оставляем работать навсегда

          Далее…

          1. Запускаем среду исполнения тестов
          2. Получаем отчет об ошибках
          3. При необходимости исправить ошибки и повторить с п. 1

          Сервер и браузеры могут жить вечно. Тесты запускаются вами или из IDE, среда исполнения подключается к серверу, выполняет в нем тесты (во всех подключенных браузерах, сама, автоматом, браузеры больше можно не трогать), вы получаете результат.
  • 0
    Интересно, а можно ли его подключить для интеграционного тестирования к рабочей среде — дабы прогнать еще и аяксики всякие?
    • 0
      Текущая версия не умеет делать асинхронные тесты. Я для этого использую различные mock'и. Уже сейчас на странице проекта есть wiki-страница описывающая методику асинхронного тестирования, но готовых бинарников пока нет, транк проекта, насколько я понимаю, сейчас не стабильный.

      Есть различные сторонние варианты использования тестов QUnit (которые умеют делать асинхронность) и js-test-driver. Они описаны на wiki проекта и комментариях к ним.
  • 0
    Около года назад смотрел на js-test-driver, штука крутая, но с mootools и prototype не дружит никак. Сейчас глянул, все еще не починили. Рр.
    • 0
      А что конкретно и как не дружило?
      • 0
        Какой-то конфликт с парсингом json. В итоге просто ничего не работает, если в коде есть операции с json.
        • 0
          Возможно из-за того, что Prototype грешит модификацией прототипов встроенных объектов JS.
          • 0
            это проблема js-test-driver, а не Prototype, в общем то
  • –3
    Спасибо. поизучаю.
    Хотя для js подобные тесты не очень нужны. Больше интересует тест на интерфейсность(или как это назвать) чем на логику…
    • 0
      Они не нужны пока пишем простые вещи. Для вещей масштаба jQuery и ExtJS такие тесты — необходимость (кстати, они у них есть).

      Для интерфейсного тестирования попробуйте Selenium.

    • 0
      Для server-side очень даже могут понадобиться.
  • +1
    +46, а в закладки 59 отправили )
    • +1
      Не у всех есть карма / заряд. Ваш Капитан.
      • +1
        точно :)
  • 0
    Хороший пост, радует полнота изложения.

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