Знакомство с фронтенд-тестированием. Часть вторая. Юнит-тестирование
24К открытий24К показов
Рассказывает Гил Тайяр, автор блога на Hackernoon
Как мы решили в первой части, юнит-тест — это код, который тестирует юниты (части) кода: функции, модули или классы. Большинство считает, что основной массой тестов должны быть юнит-тесты — но не я. На протяжении всей серии статей я буду утверждать, что важен не способ тестирования, а их количество. Тестов должно быть достаточно для того, чтобы быть уверенным в качестве предоставляемого пользователю продукта.
Юнит-тесты — это самые простые тесты для написания и самые легкие для понимания. Вся суть — подать что-то на вход юнита и проверить результат на выходе (например, на вход вы подаете параметры функции, а на выходе получаете значение).
Прим. перев. Советуем вам также прочитать нашу статью, посвящённую целям юнит-тестирования — узнаете, зачем они нужны и для чего, кроме тестирования, их можно использовать.
Кроме того, вы должны стремиться писать код таким образом, чтобы это позволяло тестировать юниты изолированно.
Юниты в приложении Calculator
Но достаточно теории, давайте взглянем на приложение Calculator, исходный код которого можно найти на GitHub. Это React-приложение, которое имеет два главных компонента: keypad
и display
. Это определенно юниты, поскольку они не зависят от других юнитов, но это React-юниты — как тестировать их, мы узнаем позже. Если вы только знакомитесь с React, почитайте нашу подборку советов для начинающих.
Причина, по которой я не использую JSX, заключается в том, что я не хотел углубляться в трансляцию кода. Все современные браузеры полностью совместимы с ES6, так почему бы мне не запустить код без трансляции? Да, я знаю, что мой код не запустится в IE, но это демо-код, так что все в порядке. В реальном проекте я бы так не сделал.
Но должен же какой-то код определять, что происходит при нажатии на цифру (1
, 5
) или оператор (+
, =
)? Как это принято сегодня, я разделил мои компоненты на презентационные (keypad
и display
) и компоненты-контейнеры — calculator-app
. Это единственный компонент в этом приложении, который имеет состояние (state), и именно этот компонент определяет, что должно отображаться на экране при нажатии на кнопку калькулятора.
Модуль-калькулятор
Но тот компонент отвечает лишь за логику отображения, а что с вычислениями? Этим занимается отдельный модуль, calculator
, который не имеет React-зависимостей. Этот модуль идеален для юнит-тестирования! Код идеально подходит для юнит-тестирования, если он не содержит I/O- и UI-зависимостей. Вы должны стараться избегать таких зависимостей в логике своих приложений.
Что означает I/O (ввод / вывод) в веб-приложениях? Там же нет файлов, баз данных и тому подобного? Да, этого нет, но есть AJAX-вызовы, localStorage и доступ к DOM. Я считаю, что все, касающееся API браузера — это I/O.
Как я отделил логику калькулятора от компонента React? В случае с калькулятором это довольно легко. Я выделил её в модуль calculator
.
Модуль очень прост — он принимает состояние калькулятора (объект) и символ (то есть цифру или оператор) и возвращает новое состояние калькулятора. Если вы когда-то использовали Redux, то увидите, что это похоже на шаблон редьюсера Redux. Но если каждое состояние калькулятора зависит от предыдущего, как получить самое первое? Просто — модуль также экспортирует initialState
, которое вы используете для инициализации калькулятора. Состояние калькулятора не является неизвестным — оно включает в себя поле с именем display
, которое и нужно показать приложению калькулятора для этого состояния.
Если вы хотите увидеть код, давайте посмотрим на начало, которое является самой важной частью, поскольку детали алгоритма не так важны:
Специфика алгоритма не так важна, как простота функции, которую экспортирует модуль — получив состояние, можно всегда проверить следующее.
Именно это мы и делаем в test-calculator
. Здесь полностью протестирована эта весьма нетривиальная логика.
Для тестирования создано множество фреймворков. Самым популярным в настоящее время является Mocha, и мы будем использовать именно его. Но не стесняйтесь использовать Jest, Jasmine, Tape или любой другой фреймворк, который вам нравится.
Тестируем юнит при помощи Mocha
Все тестирующие фреймворки схожи — вы пишете тестовый код в функциях, называемых тестовыми, и фреймворк их запускает. Конкретный код, который запускает их, обычно называется «runner».
«Runner» в Mocha — это скрипт под названием mocha
. Если вы посмотрите на package.json
в тестовом скрипте, вы увидите его там:
Это запустит все тесты в тестовой папке, которые начинаются с префикса test-
. Если вы склонируете этот репозиторий и вызовете npm install
для него, то сможете запустить npm test
и протестировать юнит самостоятельно.
При запуске будет примерно следующее:
Очевидно, что если тест не будет пройден, он будет помечен красным, и вы его немедленно исправите. Давайте посмотрим на код:
Первым делом мы импортируем mocha
и библиотеку для проверок (assert’ов) expect
. Мы импортировали функции, которые нам нужны: describe
, it
и except
.
Потом мы импортируем модуль, который тестируем — calculator
.
Затем идут тесты, которые описаны с использованием функции it
, например:
Эта функция принимает строку, описывающую тест, и функцию, которая является самим тестом. Но it
тесты не могут быть «голыми» — они должны находиться в тестовых группах, которые определяются с помощью функции describe
.
А что находится в тестовой функции? Все, что мы захотим. В данном случае мы проверяем, что исходное состояние display
равно 0
. Как мы это делаем? Мы действительно могли бы сделать что-то вроде этого:
Это сработало бы чудесно! Тест в Mocha не срабатывает, если он генерирует исключение. Это так просто. Но expect
делает его намного приятнее, ведь в ней есть множество фич, облегчающих тестирование данных — например, проверка того, что массив или объект равны определенному значению.
Это и есть суть юнит-тестирования — запуск функции или набора функций (или создание экземпляра объекта и вызов некоторых его методов, если речь идёт об ООП) и сравнение фактического результата с ожидаемым.
Как писать хорошо тестируемые юниты?
Сложной частью юнит-тестирования являются не написание тестов, а максимально возможное разбиение кода. С помощью юнит-тестов может быть протестирован тот код, у которого нет I/O-зависимостей и мало зависимостей от других модулей. И это трудно, потому что мы имеем склонность компоновать логику с UI-кодом и I/O-кодом. Но это возможно, и для этого существует много способов. Например, если код проверяет поля или группу полей, нужно объединить все проверочные функции в модуль и тестировать его.
Стоп, код работает под NodeJS?!
Невероятно важный факт — юнит-тесты работают под NodeJS! Само приложение работает в браузере, а для тестирования кода, в том числе и конечного, мы используем NodeJS.
Это возможно, потому что наш код изоморфен. Это значит, что он работает и в браузере, и под NodeJS. Как же так получилось? Если вы пишете свой код, не используя I/O, это значит, что он не делает ничего специфичного для конкретного браузера, следовательно, нет причины, по которой он бы не запускался под NodeJS. Особенно если он использует require
, поскольку require
нативно распознается как NodeJS, так и сборщиком вроде Webpack. И если вы посмотрите на package.json
, то увидите, что мы используем Webpack именно для того, чтобы связать код, использующий require
:
Итак, наш код использует require
для импортирования React и других модулей, и благодаря магии NodeJS и Webpack мы можем использовать эту модульную систему как в NodeJS, так и в браузере — NodeJS распознает require
нативно, а Webpack использует require
, чтобы объединить все модули в один большой JS-файл.
Выполнение юнит-тестов в браузере
Кстати, мы могли бы использовать другой тестовый фреймворк, Karma, для запуска нашего Mocha-кода в браузере. Однако я считаю, что если юнит-тесты могут выполняться под Node, то именно так и стоит делать. И если вы не транслируете код, то тесты выполняются очень быстро.
Но не запускать тесты в браузере нельзя, так как мы не знаем, работает ли наш код в браузере. Возможны отличия в поведении JS-кода в браузере и в NodeJS. И тут на помощь приходят E2E-тесты, о которых мы поговорим в следующей части.
Другие статьи серии
24К открытий24К показов