Сервис для
сео - оптимизаторов

Найди ошибки на сайте
Ошибки мешают продвижению сайта
Исправь ошибки на сайте
Сайт без ошибок продвигать легче
Получи новых клиентов
Новые клиенты принесут больше прибыль

Модульное тестирование и TDD в Node.js - Часть 1

  1. Установка мокко и чай
  2. Ваш первый тест
  3. Заглушки с Синоном
  4. Шпионы с Синоном
  5. Заключение

Тестирование является важной практикой в ​​разработке программного обеспечения для улучшения качества программного обеспечения. Есть много форм тестирования; ручное тестирование, приемочные испытания, модульное тестирование и некоторые другие. В этом посте мы рассмотрим модульное тестирование в Node с использованием кофе мокко тестовые рамки. Модульные тесты обычно составляют большинство тестовых пакетов. Они тестируют небольшие блоки кода, как правило, метод или функцию, изолированно . Главное, что нужно помнить, это аспект изоляции .

В этой статье мы начнем с написания модульных тестов для функции, которая просто принимает некоторые данные, возвращает некоторые результаты и не имеет никаких зависимостей. Затем мы рассмотрим два типа тестовых двойников, заглушек и шпионов, используя библиотеку Sinon. Наконец, мы рассмотрим, как тестировать асинхронный код в Mocha. Давайте начнем!

Установка мокко и чай

Чтобы установить Mocha, просто запустите:

npm установить mocha -g

В отличие от других сред тестирования JavaScript, таких как Jasmine и QUnit, Mocha не имеет библиотеки утверждений. Вместо этого Мокко позволяет вам выбирать свой собственный. Популярные библиотеки утверждений, используемые с Mocha, включают should.js , expect.js , Chai и Node встроенный модуль assert. В этом посте мы собираемся использовать Чай.

Сначала давайте создадим файл package.json и установим Chai:

touch package.json echo {}> package.json npm install chai --save-dev

Чай имеет три разных вкуса. У него есть стиль must, ожидаемый стиль и стиль assert. Все они выполняют свою работу, и выбор одного из них - это просто вопрос того, как вы хотите, чтобы язык ваших тестов читался. Лично мне нравится стиль ожидания, поэтому мы будем его использовать.

Ваш первый тест

( Исходный код для проекта )

В нашем первом примере мы будем использовать разработку через тестирование (TDD) для создания функции конструктора CartSummary, которая будет использоваться для суммирования товаров, помещенных в корзину. Короче говоря, TDD - это практика написания тестов перед реализацией для управления дизайном вашего кода. TDD практикуется в следующих шагах:

  1. Написать тест и посмотреть, как он провалится
  2. Напишите минимальное количество кода, чтобы пройти этот тест
  3. Повторение

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

// tests / part1 / cart-summary-test.js var chai = require ('chai'); varpect = chai.expect; // мы используем стиль ожидаемого в Chai var CartSummary = require ('./../../ src / part1 / cart-summary'); description ('CartSummary', function () {it ('getSubtotal () должен возвращать 0, если элементы не переданы в', function () {var cartSummary = new CartSummary ([]); ожидаемо (cartSummary.getSubtotal ()). to.equal (0);});});

Функция description используется для настройки группы тестов с именем. Я имею тенденцию выставлять модуль в качестве имени, в данном случае CartSummary. Тест написан с использованием функции it. Функция it получает описание в качестве первого аргумента того, что должен делать тестируемый модуль. Второй аргумент функции it - это функция, которая будет содержать одно или несколько утверждений (также называемых ожиданиями) с использованием Chai в этом примере. Наш первый тест просто проверяет, что промежуточный итог равен 0, если в корзине нет товаров.

Чтобы запустить этот тест, запустите mocha tests --recursive --watch из корня проекта. Флаг рекурсии найдет все файлы в подкаталогах, а флаг наблюдения будет наблюдать все ваши исходные и тестовые файлы и перезапускать тесты при их изменении. Вы должны увидеть что-то вроде этого:

Вы должны увидеть что-то вроде этого:

Наш тест не пройден, потому что мы еще не реализовали CartSummary. Давайте сделаем это.

// src / part1 / cart-summary.js function CartSummary () {} CartSummary.prototype.getSubtotal = function () {return 0; }; module.exports = CartSummary;

Здесь мы написали минимальное количество кода, чтобы пройти наш тест.

Здесь мы написали минимальное количество кода, чтобы пройти наш тест

Давайте перейдем к нашему следующему тесту.

it ('getSubtotal () должен возвращать сумму цены * количество для всех элементов'), function () {var cartSummary = new CartSummary ([{id: 1, количество: 4, цена: 50}, {id: 2, количество: 2, цена: 30}, {id: 3, количество: 1, цена: 40}]); ожидайте (cartSummary.getSubtotal ()). to.equal (300);});

Неудачный вывод показывает, какое значение getSubtotal вернул красным и какое значение мы ожидали зеленым. Давайте пересмотрим getSubtotal, чтобы наш тест прошел.

// src / part1 / cart-summary.js function CartSummary (items) {this._items = items; } CartSummary.prototype.getSubtotal = function () {if (this._items.length) {вернуть this._items.reduce (function (subtotal, item) {вернуть subtotal + = (item.quantity * item.price);}, 0); } return 0; };

Наш тест проходит! Мы успешно использовали TDD для реализации метода getSubtotal.

Заглушки с Синоном

Допустим, теперь мы хотим добавить расчет налога в CartSummary в методе getTax (). Конечное использование будет выглядеть так:

var cartSummary = new CartSummary ([/ * ... * /]); cartSummary.getTax ('NY', function () {// выполняется после завершения запроса налогового API});

Метод getTax будет использовать другой модуль, который мы создадим, называемый налогом, с методом расчета, который будет заниматься сложностями расчета налога по штатам. Несмотря на то, что мы не внедрили налог, мы все равно можем завершить наш метод getTax, пока мы идентифицируем контракт для налогового модуля. Этот контакт сообщит, что должен существовать модуль под названием tax с методом вычисления, который принимает три аргумента: промежуточный итог, состояние и функцию обратного вызова, которая будет выполняться после завершения запроса API к налогу.

Как упоминалось ранее, модульные тесты тестируют блоки в изоляции. Мы хотим протестировать наш метод getTax, изолированный от tax.calculate. Пока tax.calculate придерживается своего контракта кода или интерфейса, getTax должен работать. Что мы можем сделать, так это подделать tax.calculate при тестировании getTax с использованием заглушки, типа двойного теста, который действует как контролируемая замена. Тестовые удвоения часто сравнивают с двойниками каскадеров, поскольку они заменяют один объект другим для целей тестирования, подобно тому, как актеры и актрисы заменяются двойниками каскадеров для опасных сцен действия. Мы можем создать эту заглушку с помощью библиотеки Sinon.

Чтобы установить Sinon, запустите:

npm установить sinon --save-dev

Первое, что нам нужно сделать, прежде чем мы сможем заглушить метод tax.calculate, это определить его. Мы не должны реализовывать его детали, но метод расчета должен существовать на объекте налога.

// src / part1 / tax.js module.exports = {Рассчитать: функция (промежуточный итог, состояние, выполнено) {// реализовано позже или параллельно нашим коллегой}};

Теперь, когда tax.calculate создан, мы можем заглушить его с помощью нашей предварительно запрограммированной замены с помощью Sinon:

// tests / part1 / cart-summary-test.js // ... var sinon = require ('sinon'); var tax = require ('./../../ src / part1 / tax'); description ('getTax ()', function () {beforeEach (function () {sinon.stub (налог, «рассчитать», функция (промежуточный итог, состояние, выполнено) {setTimeout (function () {done ({количество: 30} );}, 0);});}); afterEach (function () {tax.calculate.restore ();}); это ('get Tax () должен выполнить функцию обратного вызова с суммой налога', function ( готово) {var cartSummary = new CartSummary ([{id: 1, количество: 4, цена: 50}, {id: 2, количество: 2, цена: 30}, {id: 3, количество: 1, цена: 40 }]); cartSummary.getTax ('NY', функция (taxAmount) {ожидание (taxAmount) .to.equal (30); done ();});});});

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

var stub = sinon.stub (объект, метод, func);

В этом примере я просто заглушил tax.calculate со следующим:

function (промежуточный итог, состояние, выполнено) {setTimeout (function () {done ({amount: 30});}, 0); }

Это просто функция, вызовы которой выполняются со статическим объектом налоговых сведений, содержащим сумму налога 30. setTimeout используется для имитации асинхронного поведения этого метода, поскольку в действительности он будет выполнять асинхронный вызов API для некоторой налоговой службы. Это происходит в блоке beforeEach, который выполняется перед каждым тестом. После каждого теста выполняется блок afterEach, который восстанавливает исходный tax.calculate.

Этот тест проверяет, что функция обратного вызова, переданная в getTax, выполняется с суммой налога, а не со всем объектом сведений о налогах, который передается в функцию обратного вызова для tax.calculate. Как видите, наш тест для getTax проходит, хотя мы еще не реализовали tax.calculate. Мы просто определили интерфейс этого. Пока tax.calculate поддерживает этот интерфейс, оба модуля должны работать правильно вместе.

Этот пример также демонстрирует асинхронное тестирование. Указав параметр в функции it (в этом примере вызывается done), Mocha передаст функцию и дождется ее выполнения перед завершением теста. Тест остановится и произойдет ошибка, если не выполнено в течение 2000 миллисекунд. Если бы мы не сделали это асинхронным тестом, тест завершился бы до того, как наши ожидания оправдались, что заставило бы нас думать, что все наши тесты проходят, а на самом деле это не так.

Теперь давайте напишем реализацию getTax для прохождения нашего теста:

CartSummary.prototype.getTax = function (state, done) {tax.calculate (this.getSubtotal (), state, function (taxInfo) {done (taxInfo.amount);}); };

Шпионы с Синоном

Одна из проблем нашего метода getTax заключается в том, что наш тест не проверяет, что tax.calculate вызывается с правильными промежуточными итогами и состоянием. Наш тест все равно прошел бы, если бы мы жестко закодировали значения промежуточных итогов и состояний в реализации getTax. Давай и попробуй в образец кода , Это не хорошо! Чтобы проверить, что tax.calculate вызывается с правильными аргументами, мы можем использовать шпионов Синона.

Шпион - это еще один тип двойного теста, который записывает, как используется функция. Сюда входит информация, например, с какими аргументами вызывается шпион, сколько раз вызывается шпион, и если шпион выдает ошибку. Самое замечательное в заглушках Синона состоит в том, что они построены поверх шпионов! Вот наш обновленный тест:

it ('getTax () должен выполнить функцию обратного вызова с суммой налога'), функция (выполнено) {var cartSummary = new CartSummary ([{id: 1, количество: 4, цена: 50}, {id: 2, количество: 2, цена: 30}, {id: 3, количество: 1, цена: 40}]); cartSummary.getTax ('NY', функция (taxAmount) {ожидание (taxAmount) .to.equal (30); ожидание ( tax.calculate.getCall (0) .args [0]). to.equal (300); ожидаемо (tax.calculate.getCall (0) .args [1]). to.equal ('NY'); ​​сделано ( );});});

Еще два ожидания были добавлены к этому тесту. getCall используется для получения первого вызова заглушки для tax.calculate. args содержит аргументы для этого вызова. Мы просто проверяем, что tax.calculate был вызван с правильной промежуточной суммой и состоянием в противоположность жестко закодированным значениям.

Sinon - очень мощная библиотека, предлагающая множество двойных функциональных возможностей для тестирования JavaScript как в Node, так и в браузере, которые вы найдете полезными, поэтому обязательно ознакомьтесь с документацией.

Заключение

В этом посте мы рассмотрели несколько практических примеров модульного тестирования в Node с использованием инфраструктуры тестирования Mocha, библиотеки утверждений Chai и Sinon для двойников теста в форме заглушки и шпионажа. Я надеюсь, вам понравился этот пост. Если у вас есть какие-либо вопросы, задайте их ниже или свяжитесь со мной в Twitter @ skaterdav85 ,

Часть 2 будет посвящена тестированию HTTP-запросов с помощью Nock.

Исходный код