Помните, в чем смысл модульного тестирования: чтобы определенный модуль кода реагировал на некоторые стимулы ожидаемым образом.В JS значительная часть вашего кода (если у вас нет какой-либо инфраструктуры жизненного цикла, такой как Sencha или YUI) будет либо напрямую манипулировать DOM, либо делать удаленные вызовы.Чтобы проверить эти вещи, вы просто применяете традиционные методы юнит-тестирования внедрения зависимостей и насмешек / заглушек.Это означает, что вы должны написать каждую функцию или класс, который вы хотите выполнить модульным тестированием, чтобы принимать макеты зависимых структур.
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);
Обратите внимание, что мы не только извлекаем логику из анонимной функции и присваиваем ей имя, мы такжезаставить эту функцию принимать параметр области видимости.Когда это значение равно нулю, вызовы 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.Есть пара более дисциплинированных подходов, но один рентабельный подход заключается в том, чтобы обернуть зависимости в функции-оболочки.Кодовая база будет зависеть только от этих оболочек, чьи реализации могут быть легко заменены тестовыми заглушками во время теста.