Пользователь
0,1
рейтинг
11 января 2012 в 19:04

Разработка → PhantomJS + JSCoverage + QUnit или консольные JS юнит-тесты с подсчетом покрытия

Поговорим о случае, когда нужно автоматизировать запуск тестов и сбор статистики покрытия, к примеру, для гипотетической клиентской JS библиотеки. Задача не совсем тривиальна, поскольку для нормальной работы библиотеки требуется полноценный браузер — библиотека является визуальной оберткой над стандартными компонентами формы. Библиотека должна быть написана так, чтобы все взаимодействие с ее объектами можно было производить с помощью методов, которые они предоставляют, а не только через непосредственные манипуляции с DOM (т.е. любое действие юзера может быть запущено не только событием, допустим, клика по чему-то, но и руками через метод). Но тем не менее, надо этот DOM иметь, чтобы результаты работы методов помимо изменения внутреннего состояния объектов также отображались и в DOM. В целом напоминает приложения на Sencha (ExtJS).

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

Концепт

Один из вариантов решения — JSTestDriver, о котором здесь уже писали, но в рассматриваемом случае этот вариант не подошел, поскольку всё решение должно запускаться консольно, не должно плодить ни лишних сервисов, ни браузерных окон, должно запускаться и умирать сразу по завершении работы. Плюс на данный момент JSTestDriver не умеет исключать подключенные файлы из Coverage Report, что сильно искажает картину, т.к. считать покрытие, например, для подключенной jQuery или MooTools дело лишнее. На хабре был обзор простого запуска QUnit тестов с помощью PhantomJS, но без подсчета покрытия или сборки страницы (что является необходимым как раз таки для покрытия).

С учетом уточненных требований, родилось решение на основе связки PhantomJS + JSCoverage + QUnit, размещенное мной на Google Code. Остановимся подробнее на компонентах:
  • PhantomJS — консольный (безголовый) браузер, контролируемый через JS API;
  • JSCoverage — консольный парсер JS файлов, внедряет в них для каждой исполняемой строки инкремент соотв. элемента массива для подсчета количества выполнений. Есть незначительные ограничения — участвуют только строки, которые вообще можно выполнить, т.е. не комментарии, к примеру. Для нотаций объектов и массивов учитывается только строка, где нотация началась;
  • QUnit — небольшой JS фреймворк для удобного и быстрого создания юнит-тестов, в том числе асинхронных, запускается в браузере как страничка с подключенными скриптами.

Запуск

Для корректной работы утилиты необходимо скачать и распаковать два архива с исполняемыми файлами PhantomJS (dynamic) и JSCoverage, пути к ним можно поменять в батниках, а также указать там корректное название группы тестов для выполнения. Для того, чтобы связать воедино все компоненты и был написан вспомогательный функционал, о котором идет речь. Для настройки набора тестов и скриптов для включения в запускаемую страницу используется один конфигурационный файл вида:

var config = {
    includes: [ // файлы, которые будут включены в запускаемый скрипт
        {
            file: 'lib/jquery-1.7.1.js',
            coverage: false // не включаем публичную либу в состав отчета о покрытии
        },
        {
            file: 'lib/json2.js',
            coverage: false
        },
        {
            file: 'lib/testable.js',
            coverage: true // а вот тестируемую библиотеку — включаем
        }
    ],
    testCases: [ // массив путей до тесткейсов, могут обрабатываться рекурсивно, включены будут только файлы, подходящие по маске
        {
            location: '/tests',
            pattern: /.+Test\.js/g,
            recursive: true
        }
    ],
    target: { // информация о сборочной директории
        location: '/target'
    }
};

После запуска «Самого Главного Батника» (в примере из репозитория run-tests.cmd или run-tests2.cmd в зависимости от того, какой набор планируется запускать) происходит, упрощенно, следующий ряд действий:
  1. Собрать в одну папку все подключаемые JS файлы для обработки JSCoverage'ом;
  2. Пройтись по ним JSCoverage'ом;
  3. Собрать ссылки на остальные подключаемые файлы, не участвующие в подсчете покрытия (библиотеки, сам QUnit, вспомогательные компоненты для тестирования и т.д.);
  4. Найти все файлы с тест кейсами;
  5. Собрать из описанных выше элементов страницу для выполнения в обычном для QUnit порядке;
  6. Запустить эту страницу со скриптами на выполнение из-под PhantomJS;
  7. Забрать из «окна юраузера» отчет QUnit и статистику выполнения — покрытие, кол-во выполненных, успешных и упавших тестов;
  8. Сгенерировать из всего этого отчет в человекопонятном виде;
  9. Сложить в папку отчеты, а также все необходимое для повторного запуска.

Отчеты

После проделанных манипуляций в папке с утилитой окажется подпапка /target/, в которой будут сложены:
  • test.html — та самая страница с нужным списком скриптов для запуска в webkit-подобных браузерах (ограничение PhantomJS, ему пока нельзя давать ссылки с протоколом file://), позволяет воспользоваться всем любимым функционалом dev tools для отладки кривых тестов в привычной визуальной среде (порядок такой — прогоняем консольный тест, смотрим в браузере, фиксим, прогоняем снова, смотрим в браузере обновленный вариант);
  • test-result.html — отчет о выполнении тестов, собранная статистика покрытия, без непосредственного выполнения тестов:
  • *.js.html — отчеты покрытия по каждому включенному в отчет файлу:
  • coverage.json — сырая статистика количества исполненных строк по файлам, просто на всякий случай посмотреть, что собрал JSCoverage.

Итог

+ Сбор статистики покрытия;
+ Автоматическая сборка ресурсов для проведения тестов, от пользователя требуется один раз сконфигурировать и просто запускать батник;
+ Запуск из-под консоли;
+ Standalone (для запуска не надо ничего, кроме двух программ, которые не надо конфигурировать или устанавливать — просто распаковать архивы);
+ Наглядный отчет после выполнения;
­− Тестирование происходит в условиях webkit браузера, что не позволяет проверить, например, особенности работы браузеров с массивами, с другой стороны кроссбраузерные тесты лучше гонять чем-то наподобие Selenium'а, чтобы эмулировать действия юзера, в случае рассматриваемого тула идет чисто тесты API;
­− Сборщик на данный момент работает только с относительными путями, в ближайшее время появится поддержка абсолютных путей и ссылок;
­− Нет интеграции с Maven или чем-то другим для непрерывной интеграции — в ближайших планах.

На данный момент утилиту можно рассматривать как proof of concept, который неплохо справляется со своей задачей, упрощает жизнь, но которая не лишена и недостатков. В любом случае я планирую развивать функционал по мере необходимости и по возможности. В комплекте идет также небольшая утилитка для запуска тестов от JSTestDriver в рамках описанного фреймворка, так что Вы можете гонять тесты и в IDE в PhantomJS'е, но на данный момент она не умеет запускать асинхронные тесты.
dfuse @dfuse
карма
99,7
рейтинг 0,1
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

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

  • +4
    Круто! :)
    • +1
      Согласен, круто, но… У меня есть немного полувопросов-полукомментариев.
      1. JSCoverage ведет себя не всегда адекватно (его статистику можно охарактеризовать как «очень странную»).
      2. Из опыта использования JSTestDriver могу сказать, что он достаточно «падучий» и проблема его заключается в первую очередь в том, что многократный запуск тестов в одном и том же браузере отжирает большое количество памяти. Так ли это в случае с PhantomJS?
      3. Очень длинные по времени тесты (например, те, которые тестируют очереди вызовов), периодически падали в JSTestDriver (очевидно, по таймауту). Как ведет себя PhantomJS + QUnit?
      4. Можно ли все это счастье хорошо проинтегрировать в какую-то IDE (IntelliJ IDEA/WebStorm) и выполнять навигацию по ошибкам?
      • +1
        Не круто?
        • +1
          Круто, но пока не попробую, есть вопросы.
          • +1
            Вопросы — это круто! :)
      • +1
        1. Адекватнее отчетов покрытия я все равно не видел
        2. Он поднимается и закрывается сразу по завершении тестов, это не демон и не сервис, в посте об этом было
        3. Не знаю, не проверял, надо будет внести в план
        4. В комплекте идет небольшая утилитка для запуска тестов от JSTestDriver в рамках описанного фреймворка, так что Вы можете гонять тесты и в IDE в PhantomJS'е. Дописал об этом ремарку в пост
  • +3
    Нет, ну реально круто!
  • +1
    А будет все это работать под Mac OS X?
    • 0
      PhantomJS is multi-platform, it runs on Linux, Windows, FreeBSD, and Mac OS X.

      code.google.com/p/phantomjs/

      The source code can be compiled on Linux or Windows (using Cygwin or MinGW).

      siliconforks.com/jscoverage/download.html

      По-идее, должно заработать как минимум под Linux, но я не проверял.
      • +2
        Надо будет попробовать, уж очень вкусно выглядит.
  • –4
    Подходит только для простых вариантов.
    Именно что для «гипотетической библиотеки».
    Реальный проект грузит зависимости — стили, скрипты, картинки, данные и работает в некотором окружении. Поэтому часто нельзя просто копировать JS куда-то еще.

    В свое время делал работу с JSCoverage, благо он может работать как сервер и преобразовывать скрипты на лету. Что я делал — в конфигурации апачи настраивал прокси на определенный адрес/порт/шаблон имени файла/пути.
    При запросе с адреса site.com/test.html — запускается просто qunit тест,
    а при запросе с site.com:888/test.html — запускается qunit тест со скриптами пропущенными через JSCoverage (он висит в фоне сервером)

    Таким образом по шаблону в настройках прокси отсеиваются и стандартные библиотеки. Так как они обычно лежат в фолдере отличном от скриптов проекта.
    • +4
      Есть понятие интеграционных тестов, а также вещи типа Selenium, которые тестируют в определенном окружении. Юнит тесты же предполагают отсутствие окружения и должны тестировать компоненты в отдельности.

      В данный момент такими тестами покрывается проект с кодовой базой под 2 мегабайта непожатых JS файлов, который представляет собой ExtJS-подобный фреймворк, с нуля написанный под себя.

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

      Подобный трюк меня бы не устроил именно потому, что нужно поднимать сервер, проверять жив ли он на момент запуска Maven сборки, а это лишние движения. В таком ключе проще было бы продолжать работать через JSTestDriver. В моем решении нет серверов или сервисов/демонов.

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