Как выполнить модульное тестирование блока $(function () { }) с помощью Sinon и QUnit?

У меня есть код в файле JS, который выглядит так (упрощенно, конечно):

$(function () {
  var num;
  $.getJSON('./getNumber.php', function (n) {
    num = n;
  });

  $('#id').on('click', function () { alert(num); });
});

Мне нужно написать два модульных теста:

  1. Убедитесь, что скрипт отправляет запрос ./getNumber.php на сервер при загрузке страницы.
  2. Убедитесь, что при нажатии #id num получает предупреждение.

Очевидно, мне нужен FakeXmlHTTPRequest и макет функции alert, возможно, даже шпионить за $.getJSON. Однако я не уверен, как правильно писать тесты, сохраняя при этом два атома.

Я думаю, что единственный способ сделать это - динамически вводить блок <script> для каждого теста; но я просто чувствую, что это неправильно. Как правильно? Спасибо.

Изменить: основываясь на комментариях за пределами SO, мне нужно научиться писать тестируемый Javascript вместо того, чтобы пытаться придумать тестовые примеры для чего-то с низкой тестируемостью. Если кто-нибудь может дать мне несколько советов по переписыванию этого кода, он также будет очень признателен.


person timdream    schedule 25.11.2012    source источник


Ответы (1)


На ваш вопрос нет одного правильного ответа, но есть много хороших принципов, на которых можно учиться. Рассмотрим следующий код. Я рефакторинг вашего примера. Я объясню все, что я сделал ниже.

// Define your module in a self-executing function.
// This module is now completely unit testable.
var myModule = (function () {
    var num;

    function getNum() {
        $.getJSON('./getNumber.php', function (n) {
            num = n;
        });
    }

    function alertNum() {
        alert(num);
    }

    // this returned object will by set to myModule and will publicly
    // expose the necessary functions
    return {
        getNum: getNum,
        alertNum: alertNum
    };
})();

// wire up your events
$(function() {
    $("#id").on('click', myModule.alertNum);
});

Этот пример явно излишен для такого простого кода, но он должен дать вам несколько хороших идей.

  1. Ваша основная функциональность теперь инкапсулирована в модуль. Вы сохраняете дополнительные приватные функции и переменные внутри модуля (например, num), но теперь можно протестировать все, что открыто через возвращаемый объект. (См. примечание ниже)

  2. Я подключил события вне модуля. Если ваш модуль большой, вы можете даже поместить этот код в другой скрипт. В любом случае, это разделяет ваши проблемы в более похожем на MVC подходе. Вы можете протестировать этот фрагмент кода, но в действительности это не обязательно; это должен быть очень простой jQuery, и обертывание тестов вокруг него будет немногим больше, чем тестирование библиотеки jQuery, которая уже была тщательно протестирована командой jQuery.

  3. Это может показаться странным, но не используйте селекторы jQuery внутри своего модуля! Если вашему модулю нужен элемент, передайте его как параметр функции. Есть способы заглушить этот материал, но это действительно хлопотно, и в этом нет необходимости, если вы правильно разделили свои проблемы. Большинство моих модулей (или объектов в них) заканчиваются методом init(), через который я могу передавать данные. -- Вы по-прежнему можете использовать метод jQuery .find() для своих элементов внутри вашего модуля. Это скорее суждение, которое можете сделать только вы, основываясь на конкретных потребностях вашего модуля. Это потребует от вас более подробных макетов элементов DOM в ваших тестах, но иногда это проще, чем передавать десятки элементов в init() (вместо этого просто передать основной контейнер).


Примечание. Я настоятельно рекомендую использовать RequireJS или другой загрузчик модулей AMD для управления вашими модулями. Это предотвратит попадание таких вещей, как myModule выше, в глобальное пространство имен. Тем не менее, настройка Require с QUnit немного привередлива, и может стать проблемой, которую нужно решить позже, когда вы разберетесь с модульным тестированием.

person keithjgrant    schedule 09.01.2013