Статьи

Тестирование JavaScript, когда DOM мешает

Одно дело использовать простую функцию или даже граф объектов в контролируемой среде; другое утверждение — правильно манипулировать DOM: обычно внутри окна набора тестов. Документ пустой и распределяется между тестами.

Например, классический подход к тестированию JavaScript (JsUnit) приводит к страницам, заполненным HTML, которые выполняют множество тестов один за другим и показывают зеленую или красную полосу; есть много возможностей для улучшения.

Шаг 0: макет как можно больше

Тестовые дубликаты существуют и в JavaScript: вам не нужно вызывать проблемные функции, которые манипулируют DOM или создают XMLHttpRequest. Вы можете использовать Test Double двумя способами.

Первый является динамической заменой функций:

var oldCreateElement = {};
myLibrary.createElement = function() {
    this.called = true;
    this.arguments = arguments;
};
// test code where production code will call myLibrary.createElement...
myLibrary.createElement = oldCreateElement;

Второй осуществляется через Впрыск: MyLibrary объект передается в конструктор или к сеттер тестируемого объекта. Это действительно похоже на все другие реализации DI в Java и PHP.

Я советую вам заменять эти приемы только частью вашего кода из-за принципа « Только макеты», который вы используете .

Шаг 1: избегайте отчетов через браузер

Когда среда тестирования использует страницу браузера для составления отчетов, DOM этой страницы будет иметь два применения: предоставлять приспособления для тестов и производственный код для работы, а также предоставлять отчеты конечному пользователю.

Другой подход, за которым следует jsTestDriver , заключается в создании отчетов через консоль: вы выполняете тесты из командной строки или с помощью плагина IDE . Браузер захватывается и используется для выполнения кода, но не показывает результаты: он отправляет их обратно через Ajax на сервер jsTestDriver.

В результате вы можете начать выполнение с любого места и разорвать пустой документ в тесте, не нарушая функциональные возможности платформы.

Шаг 2: избегайте программного создания элементов DOM

Время от времени вам понадобятся некоторые элементы для вашего кода. Даже если вы заглушите код, который работает в DOM, вам все равно придется где-то его тестировать или выполнять функциональные тесты, которые затрагивают большую группу объектов.

Если вы хотите создать HTML-элементы вручную, это то, что вам предназначено :

this.div = document.createElement('div');
var p = document.createElement('p');
div.appendChild(p);
p.innerHTML = "bar";
div.id = 'foo';

Скучно читать (и писать), шумно и трудно понять. Может быть, можно сократить с помощью jQuery или других библиотек, но решение jsTestDriver действительно компактно: оно включает в себя вставку HTML-кода в качестве объявления .

Из документации jsTestDriver мы видим один из специальных комментариев, которые интерпретируются для создания элементов HTML:

/*:DOC element = <div><p>foo</p></div>*/
assertNotUndefined(this.element);

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

пример

Два варианта создания узлов DOM в jsTestDriver:

  • создание фрагментов HTML ; корневой элемент хватают и ссылка делается на это для дальнейшего использования.
  • добавление HTML к реальному документу , если рабочий код вызывает документ или окно для захвата самих элементов. Конечно, вы должны попытаться ограничить эти вызовы настолько, насколько это возможно, ради простого тестирования в изоляции, но ваши библиотеки могут их вызывать (и jQuery, или ExtJS, будут.)

Вот полный тестовый пример jsTestDriver, который показывает вам функциональные возможности, которые есть в вашем распоряжении.

TestCase('tests that involve the DOM', {
    'test that a DOM node created for this test exists' : function () {
        assertUndefined(this.inputElement);
        /*:DOC inputElement = <input id="searchbox" value="Type here" /> */
        assertNotUndefined(this.inputElement);
        assertEquals('Type here', this.inputElement.value);
    },
    'test that a DOM node can be scoped to a single test' : function () {
        assertUndefined(this.inputElement);
    },
    'test that a DOM node can be appended to the document' : function () {
        /*:DOC += <input id="appendedsearchbox" value="Type here" /> */
        assertNotNull(document.getElementById('appendedsearchbox'));
    },
    'test that an appended DOM node is not global anyway' : function () {
        assertNull(document.getElementById('appendedsearchbox'));
    },
    'test that HTML code can span over multiple lines' : function() {
        /*:DOC anotherBox = <input id="anotherbox" 
                                value="Some default" /> */
        assertEquals('Some default', this.anotherBox.value);
    },
    'test multiple top-level nodes can be appended' : function() {
        /*:DOC += <div id="first"></div>  
                  <div id="second"></div> */
        assertNotNull(document.getElementById('first'));
        assertNotNull(document.getElementById('second'));
    }
});