Подтвердить что ты не робот

Тестируемая разработка веб-интерфейсов JavaScript

Это может показаться немного глупым, но на самом деле я немного запутался, как подойти к тестированию JavaScript для веб-интерфейсов. Насколько мне известно, типичная трехуровневая архитектура выглядит так:

  • Уровень базы данных
  • Уровень приложения
  • Уровень клиента

1 не вызывает беспокойства в этом вопросе. 2 содержит всю программную логику ( "бизнес-логику" ) 3 интерфейс.

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

Архитектура с тремя уровнями поддерживает это: я могу создать свой бэкэнд как REST API, который вызывается моим интерфейсом. Как подходит JS-тестирование? Для типичной трехуровневой архитектуры проверка JS (то есть JS на клиенте) не имеет большого смысла, не так ли?

Update: Я изменил формулировку вопроса "Проверка JavaScript в веб-интерфейсах" на "Тестирование разработки веб-интерфейсов JavaScript", чтобы уточнить мой вопрос.

4b9b3361

Ответ 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. Существует еще несколько дисциплинированных подходов, но один экономически эффективный подход заключается в переносе зависимостей в функции обертки. Кодовая база будет зависеть только от этих оболочек, реализация которых может быть легко заменена тестовыми заглушками во время тестирования.

Ответ 2

Существует книга под названием Test-Driven JavaScript Development от Christian Johansen, которая может вам помочь. Я только посмотрел на некоторые из образцов в книге (просто скачал образец на Kindle на днях), но это выглядит как замечательная книга, которая затрагивает эту самую проблему. Вы можете проверить это.

(Примечание: у меня нет связи с Кристианом Йохансеном и нет инвестиций в продажи книги. Просто выглядит хорошо, что решает эту проблему.)

Ответ 3

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

Эта структура JS создается в стиле ООП, поэтому я могу реализовать модульное тестирование для основных классов и компонентов. Кроме того, чтобы охватить все пользовательские взаимодействия (которые нельзя охватить с помощью модульного тестирования), я использую Selenium WebDriver для проведения интеграционного тестирования структуры визуальные компоненты и тестировать их в разных браузерах.

Итак, TDD можно применить к разработке JavaScript, если тестируемый код написан OOP-способом. Также возможен интеграционный тест (и его можно использовать для создания TDD).

Ответ 4

Посмотрите QUnit, а также на модульные тесты методов и функций JavaScript.

Ответ 5

Вы можете протестировать свое приложение с точки зрения пользователя с помощью таких инструментов, как Rational Functional Tester, инструменты HP или другое эквивалентное программное обеспечение.

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

К сожалению, эти инструменты очень дороги, поэтому могут быть другие эквиваленты (и мне было бы интересно узнать о таких инструментах).