Ответ 1
Помните, что точка модульного тестирования: обеспечить, чтобы определенный модуль кода реагировал на некоторые раздражители ожидаемым образом. В JS значительная часть вашего кода (если у вас нет какой-либо структуры жизненного цикла, такой как Sencha или YUI), будет либо напрямую манипулировать DOM, либо делать удаленные вызовы. Чтобы протестировать эти вещи, вы просто применяете традиционные методики тестирования единицы инъекции зависимостей и насмешки /stubbing. Это означает, что вы должны написать каждую функцию или класс, которые хотите выполнить unit-test, чтобы принять mocks зависимых структур.
jQuery поддерживает это, позволяя передавать XML-документ во все функции обхода. Если вы обычно пишете
$(function() { $('.bright').css('color','yellow'); }
вместо этого вы захотите написать
function processBright(scope) {
// jQuery will do the following line automatically, but for sake of clarity:
scope = scope || window.document;
$('.bright',scope).css('color','yellow');
}
$(processBright);
Обратите внимание, что мы не только вытаскиваем логику из анонимной функции и даем ей имя, мы также делаем эту функцию принимающей параметр области. Когда это значение равно null, вызовы jQuery будут работать нормально. Однако теперь у нас есть вектор для инъекции макет документа, который мы можем проверить после вызова функции. Единичный тест может выглядеть как
function shouldSetColorYellowIfClassBright() {
// arrange
var testDoc =
$('<html><body><span id="a" class="bright">test</span></body></html>');
// act
processBright(testDoc);
// assert
if (testDoc.find('#a').css('color') != 'bright')
throw TestFailed("Color property was not changed correctly.");
}
TestFailed может выглядеть следующим образом:
function TestFailed(message) {
this.message = message;
this.name = "TestFailed";
}
Ситуация аналогична удаленным вызовам, но вместо того, чтобы на самом деле вводить какой-либо объект, вы можете уйти с маскирующим заглушкой. Скажем, у вас есть эта функция:
function makeRemoteCall(data, callback) {
if (data.property == 'ok')
$.getJSON({url:'/someResource.json',callback:callback});
}
Вы проверили бы это как таковое:
// test suite setup
var getJSON = $.getJSON;
var stubCalls = [];
$.getJSON = function(args) {
stubCalls[stubCalls.length] = args.url;
}
// unit test 1
function shouldMakeRemoteCallWithOkProperty() {
// arrange
var arg = { property: 'ok' };
// act
makeRemoteCall(arg);
// assert
if (stubCalls.length != 1 || stubCalls[0] != '/someResource.json')
throw TestFailed("someResource.json was not requested once and only once.");
}
// unit test 2
function shouldNotMakeRemoteCallWithoutOkProperty() {
// arrange
var arg = { property: 'foobar' };
// act
makeRemoteCall(arg);
// assert
if (stubCalls.length != 0)
throw TestFailed(stubCalls[0] + " was called unexpectedly.");
}
// test suite teardown
$.getJSON = getJSON;
(Вы можете обернуть все это в шаблоне , чтобы не помешать глобальному пространству имен.)
Чтобы применить все это в тестовом режиме, вы просто сначала напишите эти тесты. Это простой, без излишеств и, самое главное, эффективный способ модульного тестирования JS.
Структуры, такие как qUnit, могут использоваться для управления вашими модульными тестами, но это лишь небольшая часть проблемы. Ваш код должен быть написан удобным для пользователя способом. Кроме того, инфраструктура, например Selenium, HtmlUnit, jsTestDriver или Watir/N, предназначена для тестирования интеграции, а не для модульного тестирования как такового. Наконец, ни в коем случае ваш код не должен быть объектно-ориентированным. Принципы модульного тестирования легко смешиваются с практическим применением модульного тестирования в объектно-ориентированных системах. Это отдельные, но совместимые идеи.
Стили тестирования
Я должен отметить, что здесь демонстрируются два разных стиля тестирования. Первый предполагает полное незнание реализации processBright. Это может быть использование jQuery для добавления стиля цвета, или это может быть использование собственных DOM-манипуляций. Я просто проверяю, что внешнее поведение функции соответствует ожиданиям. Во-вторых, я предполагаю знание внутренней зависимости функции (а именно $.getJSON), и эти тесты охватывают правильное взаимодействие с этой зависимостью.
Подход, который вы принимаете, зависит от вашей философии тестирования и общих приоритетов и профиля затрат и выгод вашей ситуации. Первый тест относительно чист. Второй тест прост, но относительно хрупкий; если я изменю реализацию makeRemoteCall, тест сломается. Предпочтительно предположение, что makeRemoteCall использует $.getJSON, по крайней мере, оправдано документацией makeRemoteCall. Существует еще несколько дисциплинированных подходов, но один экономически эффективный подход заключается в переносе зависимостей в функции обертки. Кодовая база будет зависеть только от этих оболочек, реализация которых может быть легко заменена тестовыми заглушками во время тестирования.