Юнит-тестирование и 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');

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

    Подробнее
    Реклама
    Комментарии 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
                        Не у всех есть карма / заряд. Ваш Капитан.
                    • 0
                      Хороший пост, радует полнота изложения.

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