Тестируем JavaScript c помощью QUnit

Отбросив все объяснения зачем писать юнит-тесты, перейдем сразу к рассмотрению вопроса как их писать с помощью QUnit.

Для начала скачаем последнюю версию библиотеки с официального сайта(она содержит в себе 2 файла js и css) и создадим обычных HTML файл, в котором будем выполнять наши тесты:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>QUnit Example</title>
  <link rel="stylesheet" href="qunit.css">
</head>
<body>
  <div id="qunit"></div>
  <script src="qunit.js"></script>
  <script src="tests.js"></script>
</body>
</html>

Файл tests.js с описанием тестов:

QUnit.test( "hello test", function() {
  ok( 1 == "1", "Passed!" );
});

Запустим HTML файл и посмотрим что у нас получилось.

Разберем этот пример более подробно: QUnit.test – метод, который вызывает тест для определенного функционала, имеет следующий синтаксис:

QUnit.test( checkFunctionName, callback)

причем, каждый функциональный тест может включать в себя множество подтестов-условий, например:

QUnit.test( "hello test", function() {
  ok( 0 == "0", "Passed!" );
  ok( 1 == "1", "Passed!" );
  ok( 2 == "2", "Passed!" );
 });

Кроме этого мы можем объединять тесты в группы, следующим образом:

module( "group digits" );
QUnit.test( "chech zero", function() {
    ok( 0 == "0", "Passed!" );
}
QUnit.test( "chech one", function() {
    ok( 1 == "1", "Passed!" );
}

Также QUnit позволяет нам проводить асинхронные тесты, для этого обрамляем условие в метод QUnit.asyncTest и вызвать метод start(), когда когда действие выполнится:

QUnit.asyncTest( "asynchronous test: one second later!", function() {
  setTimeout(function() {
    ok( true, "Passed and ready to resume!" );
    start();
  }, 1000);
});

Кроме метода start() для асинхронной работы еще есть и метод stop() – для случая когда нам снова необходимо дождаться отработки асинхронного модуля.

Теперь пройдемся по методам с помощью которых мы создаем условия:

ok( state, message ); //проверят что первое TRUE
equal(actual, expected, message);// ==
strictEqual( actual, expected, message ); // ===
deepEqual( actual, expected, message ); // === в составном типе
throws( block, expected, message ); // для теста exceptions

Также с помощью метода expect(), мы можем уточнить сколько проверок ожидаем сделать в рамках одного теста:

QUnit.asyncTest( "asynchronous test: one second later!", function() {
  expect(1);
  setTimeout(function() {
    ok( true, "Passed and ready to resume!" );
    start();
  }, 1000);
});

и тест будет не пройден, если итоговое число проверок не соответствует заданному.

Библиотека позволяет навешивать callback-функции на различные действия begin, done, log, moduleDone, moduleStart, testDone, testStart:

QUnit.begin(function(){ //do something });

QUnit дает возможность конфигурировать некоторые параметры через QUnit.config:

QUnit.config.reorder = false;

 

И напоследок бонус – то, чего я не нашел в инете – цепочка асинхронных вызовов. На хабре предложили такой вариант решения:

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

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

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

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

Т.е. ждать 2 секунды, а потом проверять. Сомнительное решение, которое не будет работать, если суммарное время асинхронных вызовов будет больше чем 2 секунды. Может оно конечно так и задумывалось?

Попробуем его немного переделать, так чтобы не нужно было привязываться к конкретному времени ожидания:

asyncTest('asynctest', function () {
  // Pause the test
  expect(3);
  var asyncNumberLeft = 2;
  function checkEnd(){
    asyncNumberLeft--;
    if(asyncNumberLeft === 0){
       start();
    }
  }

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

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

});

Но если мы действительно хотим добавить какой-то лимит по времени выполнения, то таймаут следует оставить.