Статьи

Baby Steps для Backbone.js: модульное тестирование моделей

Модульное тестирование является важной частью процесса разработки. Если вы заботитесь о состоянии приложения и качестве кода, рано или поздно вы начнете автоматизировать тесты. Мой опыт показывает, что чем раньше, тем лучше. Существует несколько подходов к модульному тестированию: `test-first` и` test-after`. По этой теме появляется много священных войн. Я бы сказал, что оба варианта работают, но «тестирование в первую очередь» или разработка на основе тестирования работает лучше для меня.

К концу дня, важно только, чтобы тесты существовали и помогли выявить ошибки регрессии. Тем не менее, разработка `test-first` помогает увидеть проблему раньше и в целом обеспечивает лучшее качество кода (что завершит священную войну).

Сегодня мы собираемся написать несколько тестов, которые охватили бы существующий класс моделей Feedback.js. Поскольку код уже написан, мы пойдем по принципу «тест после». К счастью, код довольно прост, так что это не составит проблемы для модульного тестирования. Но прежде, давайте настроим нашу тестовую инфраструктуру.

Папки и файлы

У нас будет новая папка с именем `spec`. В папке spec у меня будут папки `models` и` views`, которые будут содержать тесты для моделей и представлений.

Тесты бегуна

Я буду использовать тестовую среду Жасмин . Это очень легко установить его, что нам нужно jasmine.jsи jasmine.cssбыть размещены на соответствующих папках и настройке тестовой страницы. Тестовая страница — это простой HTML-файл, который будет отправной точкой для нашего тестирования. Если вы загрузите отдельный комплект жасмина, вы увидите SpecRunner.htmlвнутри. Это может быть легко адаптировано для пользовательских нужд. В головной части tests.htmlнам нужно сослаться на все необходимые файлы .css и .js.

<title>Feedback form specs</title>
 
<link rel="stylesheet" type="text/css" href="content/jasmine.css">
 
<script type="text/javascript" src="/scripts/libs/jquery-1.7.2.js"></script>
<script type="text/javascript" src="/scripts/libs/underscore.js"></script>
<script type="text/javascript" src="/scripts/libs/backbone.js"></script>
<script type="text/javascript" src="/scripts/libs/jasmine.js"></script>
<script type="text/javascript" src="/scripts/libs/jasmine-html.js"></script>
<script type="text/javascript" src="/scripts/libs/mock-ajax.js"></script>
 
<!-- Sources -->
<script type="text/javascript" src="/scripts/src/models/Feedback.js"></script>
 
<!-- Specs -->
<script type="text/javascript" src="/scripts/spec/models/Feedback.spec.js"></script>

Тесты жасмина по существу

Тестирование с жасмином — это весело и легко. Jasmine — это фреймворк в стиле BDD, поэтому, если вы практиковали TDD с другими фреймворками, этот стиль может сначала запутаться. Давайте рассмотрим тестовый скелет Жасмин.

describe('Jasmine spec', function () {
    var value;
 
    beforeEach(function () {
        value = 1;
    });
 
    it ('should fail', function () {
        expect(value).toBe(0);
    });
 
    describe('when value is changed', function () {
        beforeEach(function () {
            value = 0;
        });
 
        it ('should pass', function () {
            expect(value).toBe(0);
        })
    });
});

В этом примере valueнаша SUT (тестируемая система). beforeEach()Функция — это функция установки контекста, в которой инициализируется SUT (в TDD это и порядок, и действие). itФункция является частью утверждения. Здесь мы устанавливаем наши ожидания относительно того, в каком состоянии должно быть SUT. Обратите внимание, что beforeEachони вложены describe, так что вы настраиваете SUT в зависимости от случая.

Написание некоторых тестов

Единственная функциональность, которую
Feedback.jsмодель содержит в настоящее время, это проверка. Давайте проверим это.

describe('Feedback.js spec', function () {
    var model;
 
    beforeEach(function () {
        model = new Feedback();
    });
 
    describe('when model is validating', function () {
        var errors;
    });
});

Это с чего-то начать. Он не делает никаких утверждений, поэтому теперь мы добавим несколько реальных случаев. В первом случае атрибуты `email` и` feedback` отсутствуют.

describe('when email and feedback fields are absent', function () {
    beforeEach(function () {
        errors = model.validate({});
    });
 
    it ('should have 2 errors', function () {
        expect(errors.length).toBe(2);
    });
 
    it ('should have email fields as invalid', function () {
        expect(errors[0].name).toBe('email');
    });
 
    it ('should have feedback field as invalid', function () {
        expect(errors[1].name).toBe('feedback');
    });
});

Возможно, что пользователь положил письмо, но забыл об обратной связи.

describe('when email is set, but feedback is absent', function () {
    beforeEach(function () {
        errors = model.validate({ email: '[email protected]'});
    });
 
    it ('should have 1 error', function () {
        expect(errors.length).toBe(1);
    });
 
    it ('should have feedback field as invalid', function () {
        expect(errors[0].name).toBe('feedback');
    });
 
    it ('should have error message', function () {
        expect(errors[0].message).toBeDefined();
    });
});

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

describe('when feedback is set, but email is absent', function () {
    beforeEach(function () {
        errors = model.validate({ feedback: 'TDD is awesome'});
    });
 
    it ('should have 1 error', function () {
        expect(errors.length).toBe(1);
    });
 
    it ('should have email field as invalid', function () {
        expect(errors[0].name).toBe('email');
    });
 
    it ('should have error message', function () {
        expect(errors[0].message).toBeDefined();
    });
});

Отчет об испытаниях

Если вы сейчас попытаетесь запустить test.htmlв браузере, у вас будет что-то подобное.

Выводы

Тестирование Backbone.Model — довольно простая вещь. Это не более чем тестирование бизнес-логики, которая может находиться внутри. Тестирование представлений немного сложнее, но мы увидим, как это сделать в следующий раз.