QUnit , разработанный командой jQuery, является отличной средой для модульного тестирования вашего JavaScript. В этом уроке я расскажу, что конкретно представляет собой QUnit, и почему вам следует тщательно проверять свой код.
Что такое QUnit?
QUnit — это мощная среда модульного тестирования JavaScript, которая помогает вам отлаживать код. Он написан членами команды jQuery и является официальным набором тестов для jQuery. Но QUnit достаточно универсален для тестирования любого обычного кода JavaScript, и он даже способен тестировать серверный JavaScript с помощью некоторого механизма JavaScript, такого как Rhino или V8.
Если вы не знакомы с идеей «модульного тестирования», не беспокойтесь. Это не так сложно понять
В компьютерном программировании модульное тестирование — это метод проверки и валидации программного обеспечения, при котором программист проверяет, пригодны ли отдельные единицы исходного кода для использования. Единица — это самая маленькая тестируемая часть приложения. В процедурном программировании единица может быть отдельной функцией или процедурой.
Это цитата из Википедии. Проще говоря, вы пишете тесты для каждой функциональности вашего кода, и если все эти тесты пройдены, вы можете быть уверены, что код не будет содержать ошибок (в основном, это зависит от того, насколько тщательны ваши тесты).
Почему вы должны проверить свой код
Если вы ранее не писали никаких модульных тестов, вы, вероятно, просто применяете свой код непосредственно к веб-сайту, нажимаете на какое-то время, чтобы увидеть, не возникает ли какая-либо проблема, и попробуйте исправить ее, как только вы ее обнаружите. Есть много проблем с этим методом.
Во-первых, это очень утомительно. Нажатие на самом деле не легкая работа, потому что вы должны убедиться, что все нажали, и очень вероятно, что вы пропустите одну или две вещи. Во-вторых, все, что вы сделали для тестирования, нельзя использовать повторно, а это значит, что найти регрессию непросто. Что такое регрессия? Представьте, что вы написали некоторый код и протестировали его, исправили все найденные ошибки и опубликовали его. Затем пользователь отправляет отзыв о новых ошибках и запрашивает новые функции. Вы возвращаетесь к коду, исправляете эти новые ошибки и добавляете эти новые функции. Что может произойти дальше, это то, что некоторые старые ошибки снова появляются, которые называются «регрессиями». Видите, теперь вы должны снова щелкнуть мышью, и скорее всего, вы больше не найдете эти старые ошибки; даже если вы это сделаете, пройдет некоторое время, прежде чем вы поймете, что проблема вызвана регрессией. В модульном тестировании вы пишете тесты для поиска ошибок, а после изменения кода вы снова фильтруете его через тесты. Если возникает регрессия, некоторые тесты обязательно будут провалены, и вы легко сможете их определить, зная, какая часть кода содержит ошибку. Поскольку вы знаете, что вы только что изменили, это легко исправить.
Еще одно преимущество модульного тестирования особенно для веб-разработки: оно облегчает тестирование кросс-браузерной совместимости. Просто запустите ваши тесты в разных браузерах, и если проблема возникает в одном браузере, вы исправите ее и снова запустите эти тесты, убедившись, что в других браузерах это не приводит к регрессии. Вы можете быть уверены, что все целевые браузеры поддерживаются, как только они все пройдут тесты.
Я хотел бы упомянуть один из проектов Джона Резига : TestSwarm . Он выводит модульное тестирование JavaScript на новый уровень, распространяя его. Это веб-сайт, который содержит множество тестов, каждый может зайти туда, запустить некоторые тесты и вернуть результат обратно на сервер. Таким образом, код можно очень быстро протестировать в разных браузерах и даже на разных платформах.
Как написать модульные тесты с помощью QUnit
Итак, как вы пишете модульные тесты с помощью QUnit? Во-первых, вам нужно настроить среду тестирования:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
<!DOCTYPE html>
<html>
<head>
<title>QUnit Test Suite</title>
<link rel=»stylesheet» href=»http://github.com/jquery/qunit/raw/master/qunit/qunit.css» type=»text/css» media=»screen»>
<script type=»text/javascript» src=»http://github.com/jquery/qunit/raw/master/qunit/qunit.js»></script>
<!— Your project file goes here —>
<script type=»text/javascript» src=»myProject.js»></script>
<!— Your tests file goes here —>
<script type=»text/javascript» src=»myTests.js»></script>
</head>
<body>
<h1 id=»qunit-header»>QUnit Test Suite</h1>
<h2 id=»qunit-banner»></h2>
<div id=»qunit-testrunner-toolbar»></div>
<h2 id=»qunit-userAgent»></h2>
<ol id=»qunit-tests»></ol>
</body>
</html>
|
Как видите, здесь используется размещенная версия фреймворка QUnit .
Код, который будет тестироваться, должен быть помещен в myProject.js, а ваши тесты — в myTests.js. Чтобы запустить эти тесты, просто откройте этот HTML-файл в браузере. Теперь пришло время написать несколько тестов.
Строительные блоки модульных тестов являются утверждениями.
Утверждение — это утверждение, которое предсказывает возвращаемый результат вашего кода. Если прогноз неверен, утверждение не выполнено, и вы знаете, что что-то пошло не так.
Чтобы выполнить утверждения, вы должны поместить их в контрольный пример:
01
02
03
04
05
06
07
08
09
10
11
12
|
// Let’s test this function
function isEven(val) {
return val % 2 === 0;
}
test(‘isEven()’, function() {
ok(isEven(0), ‘Zero is an even number’);
ok(isEven(2), ‘So is two’);
ok(isEven(-4), ‘So is negative four’);
ok(!isEven(1), ‘One is not an even number’);
ok(!isEven(-7), ‘Neither is negative seven’);
})
|
Здесь мы определили функцию isEven, которая определяет, является ли число четным, и мы хотим протестировать эту функцию, чтобы убедиться, что она не возвращает неправильных ответов.
Сначала мы вызываем test (), который создает тестовый пример; первый параметр — это строка, которая будет отображаться в результате, а второй параметр — это функция обратного вызова, которая содержит наши утверждения. Эта функция обратного вызова будет вызвана после запуска QUnit.
Мы написали пять утверждений, все из которых являются логическими. Булево утверждение ожидает, что его первый параметр будет истинным. Второй параметр также является сообщением, которое будет отображаться в результате.
Вот что вы получите после запуска теста:
Поскольку все эти утверждения успешно прошли, мы можем быть уверены, что isEven () будет работать так, как ожидалось.
Давайте посмотрим, что произойдет, если утверждение не удалось.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
// Let’s test this function
function isEven(val) {
return val % 2 === 0;
}
test(‘isEven()’, function() {
ok(isEven(0), ‘Zero is an even number’);
ok(isEven(2), ‘So is two’);
ok(isEven(-4), ‘So is negative four’);
ok(!isEven(1), ‘One is not an even number’);
ok(!isEven(-7), ‘Neither does negative seven’);
// Fails
ok(isEven(3), ‘Three is an even number’);
})
|
Вот результат:
Утверждение не удалось, потому что мы сознательно написали его неправильно, но в вашем собственном проекте, если тест не пройден, и все утверждения верны, вы знаете, что была найдена ошибка.
Больше утверждений
ok () — не единственное утверждение, которое предоставляет QUnit. Существуют и другие виды утверждений, которые полезны при тестировании вашего проекта:
Утверждение сравнения
Утверждение сравнения equals () ожидает, что его первый параметр (который является фактическим значением) равен его второму параметру (который является ожидаемым значением). Это похоже на ok (), но выводит как фактические, так и ожидаемые значения, что значительно упрощает отладку. Как и ok (), он принимает необязательный третий параметр в качестве сообщения для отображения.
Так что вместо:
1
2
3
|
test(‘assertions’, function() {
ok( 1 == 1, ‘one equals one’);
})
|
Вы должны написать:
1
2
3
|
test(‘assertions’, function() {
equals( 1, 1, ‘one equals one’);
})
|
Обратите внимание на последний «1», который является значением сравнения.
И если значения не равны:
1
2
3
|
test(‘assertions’, function() {
equals( 2, 1, ‘one equals one’);
})
|
Это дает намного больше информации, делая жизнь намного проще.
Утверждение сравнения использует «==» для сравнения своих параметров, поэтому оно не обрабатывает сравнение массивов или объектов:
1
2
3
4
5
6
|
test(‘test’, function() {
equals( {}, {}, ‘fails, these are different objects’);
equals( {a: 1}, {a: 1} , ‘fails’);
equals( [], [], ‘fails, there are different arrays’);
equals( [1], [1], ‘fails’);
})
|
Чтобы проверить это равенство, QUnit предлагает еще одно утверждение: идентичное утверждение .
Одинаковое утверждение
Идентичное утверждение, same (), ожидает те же параметры, что и equals (), но это глубокое рекурсивное утверждение сравнения, которое работает не только с примитивными типами, но также с массивами и объектами. Утверждения в предыдущем примере все пройдут, если вы измените их на идентичные утверждения:
1
2
3
4
5
6
|
test(‘test’, function() {
same( {}, {}, ‘passes, objects have the same content’);
same( {a: 1}, {a: 1} , ‘passes’);
same( [], [], ‘passes, arrays have the same content’);
same( [1], [1], ‘passes’);
})
|
Обратите внимание, что same () использует ‘===’ для сравнения, когда это возможно, поэтому это пригодится при сравнении специальных значений:
1
2
3
4
5
6
|
test(‘test’, function() {
equals( 0, false, ‘true’);
same( 0, false, ‘false’);
equals( null, undefined, ‘true’);
same( null, undefined, ‘false’);
})
|
Структурируйте свои утверждения
Поместить все утверждения в один тестовый пример — это действительно плохая идея, потому что его очень сложно поддерживать, и он не возвращает чистый результат. Что вы должны сделать, это структурировать их, поместить их в разные тестовые наборы, каждый из которых нацелен на одну функциональность.
Вы даже можете организовать тестовые случаи в разные модули, вызвав функцию модуля:
1
2
3
4
5
6
7
|
module(‘Module A’);
test(‘a test’, function() {});
test(‘an another test’, function() {});
module(‘Module B’);
test(‘a test’, function() {});
test(‘an another test’, function() {});
|
Асинхронный тест
В предыдущих примерах все утверждения вызываются синхронно, что означает, что они выполняются одно за другим. В реальном мире есть также много асинхронных функций, таких как ajax-вызовы или функции, вызываемые setTimeout () и setInterval (). Как мы можем проверить эти виды функций? QUnit предоставляет специальный вид теста, называемый «асинхронный тест», который предназначен для асинхронного тестирования:
Давайте сначала попробуем написать это регулярно:
1
2
3
4
5
|
test(‘asynchronous test’, function() {
setTimeout(function() {
ok(true);
}, 100)
})
|
Видеть? Это как если бы мы не писали никаких утверждений. Это связано с тем, что утверждение выполнялось асинхронно, к тому времени, когда оно вызывалось, контрольный пример уже завершился.
Вот правильная версия:
01
02
03
04
05
06
07
08
09
10
11
12
|
test(‘asynchronous test’, function() {
// Pause the test first
stop();
setTimeout(function() {
ok(true);
// After the assertion has been called,
// continue the test
start();
}, 100)
})
|
Здесь мы используем stop (), чтобы приостановить тестовый случай, и после того, как утверждение было вызвано, мы используем start () для продолжения.
Вызов stop () сразу после вызова test () довольно распространен; поэтому QUnit предоставляет ярлык: asyncTest (). Вы можете переписать предыдущий пример так:
01
02
03
04
05
06
07
08
09
10
11
|
asyncTest(‘asynchronous test’, function() {
// The test is automatically paused
setTimeout(function() {
ok(true);
// After the assertion has been called,
// continue the test
start();
}, 100)
})
|
Следует обратить внимание на одну вещь: setTimeout () всегда будет вызывать свою функцию обратного вызова, но что делать, если это пользовательская функция (например, вызов ajax). Как вы можете быть уверены, что функция обратного вызова будет вызвана? И если обратный вызов не вызывается, start () вызываться не будет, и весь модуль тестирования будет зависать:
Итак, вот что вы делаете:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
// A custom function
function ajax(successCallback) {
$.ajax({
url: ‘server.php’,
success: successCallback
});
}
test(‘asynchronous test’, function() {
// Pause the test, and fail it if start() isn’t called after one second
stop(1000);
ajax(function() {
// …asynchronous assertions
start();
})
})
|
Вы передаете тайм-аут для stop (), который сообщает QUnit: «если start () не вызывается после этого тайм-аута, вы должны провалить этот тест». Вы можете быть уверены, что все тестирование не будет зависать, и вы будете уведомлены, если что-то пойдет не так.
Как насчет нескольких асинхронных функций? Где вы положили начало ()? Вы помещаете это в setTimeout ():
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// A custom function
function ajax(successCallback) {
$.ajax({
url: ‘server.php’,
success: successCallback
});
}
test(‘asynchronous test’, function() {
// Pause the test
stop();
ajax(function() {
// …asynchronous assertions
})
ajax(function() {
// …asynchronous assertions
})
setTimeout(function() {
start();
}, 2000);
})
|
Время ожидания должно быть достаточно продолжительным, чтобы можно было вызывать оба обратных вызова перед продолжением теста. Но что, если один из обратных вызовов не вызывается? Как ты можешь это знать? Вот где ожидаем, что ожидается ():
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
// A custom function
function ajax(successCallback) {
$.ajax({
url: ‘server.php’,
success: successCallback
});
}
test(‘asynchronous test’, function() {
// Pause the test
stop();
// Tell QUnit that you expect three assertions to run
expect(3);
ajax(function() {
ok(true);
})
ajax(function() {
ok(true);
ok(true);
})
setTimeout(function() {
start();
}, 2000);
})
|
Вы передаете число, чтобы ожидать (), чтобы сообщить QUnit, что вы ожидаете, что X будет выполнять много подтверждений, если одно из утверждений не будет вызвано, число не будет совпадать, и вы будете уведомлены, что что-то пошло не так.
Существует также ярлык для ожидаемого (): вы просто передаете число в качестве второго параметра для test () или asyncTest ():
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
// A custom function
function ajax(successCallback) {
$.ajax({
url: ‘server.php’,
success: successCallback
});
}
// Tell QUnit that you expect three assertion to run
test(‘asynchronous test’, 3, function() {
// Pause the test
stop();
ajax(function() {
ok(true);
})
ajax(function() {
ok(true);
ok(true);
})
setTimeout(function() {
start();
}, 2000);
})
|
Вывод
Это все, что вам нужно знать, чтобы начать работу с QUnit. Модульное тестирование — отличный способ проверить ваш код перед его публикацией. Если вы раньше не писали юнит-тесты, самое время начать! Спасибо за прочтение!
- Подпишитесь на нас в Твиттере или подпишитесь на ленту Nettuts + RSS для получения лучших учебных материалов по веб-разработке.