QUnit. Тестирование javascript кода

    Наткнулся вчера на этот инструмент и не смог пройти мимо, провел ночь за написанием тестов, а теперь хочу поделиться находкой. QUnit — это библиотека от разработчиков jQuery, позволяющая писать unit-тесты для кода на javascript. Удобна в использовании, ничего лишнего, осваивается за 20 минут, выгода от применения — колоссальная.

    Самым нетерпеливым сразу ссылки:
    Официальная документация на сайте jquery: docs.jquery.com/QUnit
    Реальные примеры тестов (для модулей jquery): view.jquery.com/trunk/jquery/test/unit
    Руководство для начинающих (англ): www.swift-lizard.com/2009/11/24/test-driven-development-with-jquery-qunit
    Система распределенного тестирования (гениально и просто): testswarm.com

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

    Зачем писать unit-тесты?


    Представим себе обычный цикл разработки: получили задачу, решили её, протестировали, починили баги и выпустили версию. Затем получили баг-репорты и фиче-реквесты и приступили к новому циклу разработки. По завершению этого цикла нам снова надо будет проверить, что всё то, что было реализовано ранее, по прежнему работает — провести регрессионное тестирование. И проводить его надо будет для каждого нового цикла разработки. По мере разрастания проекта на это будет уходить всё больше и больше времени. А как происходит регрессионное тестирование в web-проектах? Кликаем мышкой по кнопкам и ссылкам. В каждом браузере, для каждой фичи, на каждом цикле разработки. Нашли баг, поправили, обновляем страницу и снова кликаем, кликаем, кликаем.

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

    Касательно web-разработки есть ещё одно огромное преимущество — запуск тестов под разными платформами и браузерами. Больше нет нужды проверять дотошно, как этот кусок кода будет работать в msie, понравится ли он опере, а как к нему отнесется сафари. Достаточно написать тест, который проверит функциональность. Более того, эту работу можно распределить между обычными пользователями, хороший пример такой функциональности — testswarm.com.

    Как использовать QUnit


    Это очень просто: понядобятся два файла:
    QUnit.js и QUnit.css, а также новый html документ примерно такого содержания:
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
       "http://www.w3.org/TR/html4/loose.dtd">
    <html>
     <head>
      <link rel="stylesheet" href="qunit.css" type="text/css" media="screen">
      <script type="text/javascript" src="qunit.js"></script>
      <script type="text/javascript" src="your-code-for-testing.js"></script>
      <script type="text/javascript" src="your-tests.js"></script>
      </script>
     </head>
     <body>
      <h1 id="qunit-header">QUnit test env</h1>
      <h2 id="qunit-banner"></h2>
      <h2 id="qunit-userAgent"></h2>
      <ol id="qunit-tests">
      </ol>
     </body>
    </html>


    * This source code was highlighted with Source Code Highlighter.


    Теперь подключаем свой код и можно писать тесты.

    Как писать тесты


    Это проще чем кажется. Давайте протестируем функцию trim, которая удаляет пробелы и табы на концах строки. Вот её код:
    function trim(text) {
      return (text || "").replace(/^\s+|\s+$/g, "");
    }


    * This source code was highlighted with Source Code Highlighter.


    А вот так её можно протестировать:

    test('trim()', function () {
      equals(trim(''), '', 'Пустая строка');
      ok(trim('   ') === '', 'Строка из пробельных символов');
      same(trim(), '', 'Без параметра');

      equals(trim(' x'), 'x', 'Начальные пробелы');
      equals(trim('x '), 'x', 'Концевые пробелы');
      equals(trim(' x '), 'x', 'Пробелы с обоих концов');
      equals(trim('    x  '), 'x', 'Табы');
      equals(trim('    x   y  '), 'x   y', 'Табы и пробелы внутри строки не трогаем');
    });


    * This source code was highlighted with Source Code Highlighter.


    Разберем пример построчно. В первой строке вызов функции test. Первым параметром обозначаем функционал который тестируем. Последним — тестирующую функцию. Внутри этой функции производятся различные проверки. В данном случае мы проверяем соответствие результата выполнения функции и ожидаемой строки. Для проверки на соответствие используются функции:
    • equals — проверяет равенство первых двух параметров (нестрогая проверка, только для скалярных величин)
    • ok — истинность первого параметра
    • same — строгая проверка на равенство первых двух параметров (проверяет также равенство двух массивов и объектов)

    Последним параметром функции принимают описание тестового случая.
    В результате этой проверки получаем следующую картину:

    Все тесты пройдены.

    Как протестировать ajax? Асинхронные проверки.



    С синхронными функциями просто. А что с асинхронными? Очевидно, для асинхронности мы должны остановить нормальный поток управления и по окончанию теста возобновить его. Этому служат функции stop() и start(). Вот простой пример:

    test('async', function () {
      // Останавливаем поток выполнения на этом тесте
      stop();

      setTimeout(function () {
        ok(true);

        // По завершению теста
        // возобновляем работу тестировщика
        start();
      }, 500);
    });


    * This source code was highlighted with Source Code Highlighter.


    Чтобы не вызывать всякий раз stop(); предусмотрен следующий вариант функции:

    asyncTest('async', function () {
      // поток остановлен автоматически

      setTimeout(function () {
        ok(true);

        // Возобновляем конечно же вручную
        start();
      }, 500);
    });


    * This source code was highlighted with Source Code Highlighter.


    А что если надо вызвать несколько асинхронных проверок в одном тесте? Когда в этом случае «стартовать» поток? Решение предлагается такое:

    asyncTest('asynctest', function () {
      // Pause the test
      expect(3);

      $.get(function () {
        // асинхронные проверки
        ok(true);
      });

      $.get(function () {
        // другие асинхронные проверки
        ok(true);
        ok(true);
      });

      setTimeout(function () {
        start();
      }, 2000);
    });


    * This source code was highlighted with Source Code Highlighter.


    Поток стартуется через 2 секунды. За это время должны пройти три проверки (вызов expect сообщает об этом тестирующей программе). Кстати, вместо вызова expect можно передавать второй числовой параметр функции test (asyncTest). Поведение будет идентичным предыдущему примеру.

    Группировка тестов по модулям


    QUnit позволяет сгруппировать тесты по модулям. Для этого достаточно вызвать функцию module('Название модуля или группы тестов') непосредственно перед вызовом тестов. Это удобно.

    Резюме


    Вот, в принципе и всё что нужно для того, чтобы начать тестировать свой код в автоматическом режиме. За дополнительной информацией обращаться сюда: docs.jquery.com/QUnit
    Очень хорошие примеры тестов можно найти здесь (это тесты для core jquery).
    Спасибо за внимание.
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 23
    • –6
      Ну наконец то на Хабре опубликовали полезный пост.
      Спасибо.
      • +2
        спасибо за статью. в закладки одноззначно
        • +2
          а где можно почитать побольше про юнит тесты? у меня возникла проблема с ними — на этапе разработки слишком много времени уходит на написание фикстур и т.д. Как правильно пользовать юнит тесты в условиях быстро меняющихся условий?
          • +2
            JS Ninja — как раз ничанается с концепции юниттестов.
          • 0
            хороший вопрос.
            в процессе работы над очередной версией компонента/проекта имеет смысл писать тесты на неизменяемый функционал — отправка письма, доступ к данным и сервисам.
            бизнес-логику, которая сильно подвержена изменениям лучше покрывать тестами выборочно и ближе к концу текущей версии.
            если бизнес-логика сильно меняется от версии к версии, то тут надо решить что важнее — время разработки или стабильность релизов, хотя чаще всего можно найти компромис.
            • 0
              А есть ли смысл тратить время, покрывая юнит тестами мелкий функционал, особенно отправку писем. Это достаточно просто проверяется и врядли сломается. А вот как раз бизнес логика, где полно связаных компонентов, вот тут и возникают проблемы, с тем, что после изменений система работает, но с ошибкой. Из моего опыта чаще всего появляются баги:
              1) верстка — тут уж только визуально, заодно и тестирование функционала
              2)SQl скрипты — из за ошибки в скрипте берутся либо не те данные, либо не в правильном порядке, либо сохраняются нетуда, ХЗ как такое проверить и протестировать
              3) Логика — чаще всего в операторах IF и switch. тут как раз, как я понимаю, и помогут юнит тесты.
              4) плохие входные данные — тут опять же помогут юнит тесты.
              • +2
                > 2)SQl скрипты — из за ошибки в скрипте берутся либо не те данные, либо не в правильном порядке, либо сохраняются нетуда, ХЗ как такое проверить и протестировать

                А в чем проблема? При запуске тестов создается временная база данных, заполняется тестовыми данными и проверяется работоспособность. По окончании база грохается.
                • 0
                  я говорил не про мелкий функционал, а про неизменяемый. отправка письма в интернет-магазине является критичным функционалом и может отваливаться когда захочет.
                  про верстку говорить не буду — с ней все понятно.
                  про доступ к данным я упомянул — я создаю тестовую БД и на ней тестирую.
                  логику — ближе к концу итерации. только логика — это не if и switch ;)
                  валидация — само собой.
              • +2
                Кстати по поводу правильного использования. Один из методов — разработка через тестирование. По ссылке все достаточно коротко и понятно написано.
                • 0
                  Да, согласен с Вами, писать тесты до самого функциональности — хорошая практика. К сожалению, не всегда получается применить в жизни.
                  • 0
                    Могу даже уточнить — практически никогда не получается. Сам применяю только когда пишу для себя — медленно, аккуратно и без меняющегося непрерывно ТЗ. Зато как потом работает и удобство по добавлению новых функций/исправлению багов выше всяких похвал.

                    Но даже когда не получается этот метод использовать хорошо о нем помнить. Всегда можно написать таким методом не весь код, а только «ядро» или потенциально проблемные участки.
                    • 0
                      Нужно стремиться «покрыть» код тестами как можно полнее. Такое покрытие дает большой плюс при развитии уже внедренного продукта. Например, создали мы библиотеку, внедрили ее в свой проект, раздали в бесплатное пользование=). Все заработало, люди пользуются. Со временем оптимизируем ее и запускаем тесты на оптимизированной библиотеке. Если тесты прошли — значит новую версию с большой долей уверенности можно внедрять. Если нет — высока вероятность, что расти будет багтрак.
              • 0
                Отличный топик! Я сам хотел написать про qunit, но вы меня опередили :)
                • 0
                  Всегда можно написать лучше ;)
                  Читаю комменты и понимаю, что многие темы не раскрыты, например тестирование до разработки, либо тестирование в рабочем окружении.
                • +3
                  А мне больше jspec нравится (не путать с jsspec): github.com/visionmedia/jspec

                  Умеет все то же самое (а, вероятно, и больше, на qunit детально не смотрел: как там у него с загрузкой html-фикстур, иерархией тестов с общими подготовительными действиями?), + очень приятный синтаксис.

                  Если кого смутит, что там как будто тесты не на js нужно писать — это не так, там можно и на js писать, просто для сокращения количества писанины там препроцессор сделан.

                  Еще есть jsTestDriver ( code.google.com/p/js-test-driver/ ), штука позволяет запускать js-юнит-тесты (втч написанные на qunit) сразу в нескольких браузерах автоматом — из командной строки или по нажатии кнопки в плагине к эклипсу, и передавать результаты в браузер, в командную строку или эклипсовскому плагину. Причем запускать тесты можно даже на браузерах, которые расположены на другом компьютере. Это удобнее, чем в браузере смотреть, и еще более все автоматизируется. + Удивительно простая установка и настройка, все сразу работает. Только 1 минус — с mootools & prototype не работает ну никак, поэтому не использую)
                  • 0
                    Спасибо за ссылки. Надо попробовать.
                    • 0
                      jspec 404 теперь выдаёт (
                    • 0
                      Также используем QUnit. У него есть странная, но похоже фундаментальная проблема — поскольку тесты запускаются в отдельном окружении, то часто глючат связи с плагинами. Чаще всего у нас проблемы с jQuery.Cookie — плагин не инициализируется в тестовом окружении. «Голые» же тесты — на ура, простенько и аккуратно.
                      • +1
                        При всём моём уважении к jQuery, JSpec мне нравится больше, хотя бы более красивой страницей вывода результатов :).
                        • 0
                          Вот тут в конце поста дан альтернативный вариант запуска теста асинхронной цепочки вызовов.

                          • 0
                            Для знакомства весьма подробно.

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