Зачем нам писать тесты для нашего кода?

Когда более одного разработчика активно вносят изменения в кодовую базу, обычно возникают проблемы и ошибки. Также трудно определить, кто допустил ошибки в коде, или точно узнать, что является основной причиной ошибок. Поэтому было бы хорошо принять превентивные меры, прежде чем внедрять что-либо из этого в кодовую базу. Это можно сделать, написав тесты, отдельные разработчики могут протестировать их локально на своих машинах, или автоматические наборы тестов также могут быть настроены в конвейерах CI/CD, которые запускаются при фиксации кода. Еще одно преимущество написания тестов заключается в том, что когда мы разрабатываем функции для приложений, мы склонны писать более качественные и чистые функции, поскольку понимаем, что в конечном итоге нам придется писать для них тесты.

Различные типы тестов

Существуют различные типы тестов, наиболее часто встречающиеся:

Модульный тест
Модульный тест используется для тестирования наименьшей единицы исходного кода (например, функций или методов). Это самый простой в реализации и самый распространенный тест среди всех типов.

Интеграционное тестирование
Оно предназначено для проверки перекрестной связи между различными компонентами или модулями в базе кода, например функции проверки подлинности, которые включают различные части архитектуры приложения. Интеграционные тесты строятся на основе отдельных модульных тестов.

Сквозное тестирование
Сквозное тестирование, как следует из названия, предназначено для проверки рабочего процесса программного обеспечения от начала до конца. Это может быть очень сложно, когда приложение становится больше, и поэтому многие компании все еще проводят ручное тестирование. Процесс можно начать с запуска браузера и ввода URL-адреса веб-приложения в адресной строке …, который управляется пользовательским интерфейсом. Однако существуют также такие инструменты, как Selenium, Cypress и Protractor, которые помогают автоматизировать это сквозное тестирование, хотя его настройка может занять довольно много времени.

Существует довольно много библиотек тестирования, которые служат для разных целей и для разных языков программирования. В этой статье мы сосредоточимся на тестировании нашего кода JavaScript. В частности, Джест — главный герой этой статьи.

Шутка: что и почему?

Jest — популярная (особенно для библиотеки React) библиотека для тестирования JavaScript. Он предоставляет широкий спектр методов и функций, которые охватывают многие части, включая утверждения, макеты и шпионы, покрытие кода и т. Д. В процессе тестирования. Когда вы используете фреймворк create-react-app, Jest уже встроен. В сегодняшней статье мы рассмотрим простую настройку Jest для вашего кода JavaScript и то, как мы можем начать локальное тестирование функций нашего приложения.

Быстрая установка

Сначала мы инициализируем рабочий каталог с помощью npm.

npm init -y

Флаг -y в основном означает автоматический прием подсказок от npm init (вместо нажатия клавиши ввода для каждой подсказки).

Далее мы устанавливаем Jest из npm. Нам нужно только установить Jest в качестве зависимостей для разработчиков, потому что это требуется только на этапе разработки.

npm install jest — save-dev

После установки вы должны увидеть, что пакет Jest включен в devDependencies файла package.json.

Теперь давайте начнем с нашего первого примера:

script1.js

Сценарий 1 просто складывает два числа и возвращает сумму.

Чтобы протестировать script1.js, мы создаем еще один файл с именем «script1.test.js» (было бы хорошо следовать соглашению об именах тестовых файлов для скриптов). В этот тестовый скрипт мы можем добавить следующий код JavaScript:

Это означает, что мы импортируем функцию addNums из script1.js и выполняем тест в этом сценарии. Вы можете написать «test» или его псевдоним «it» (который мы использовали в скрипте) из Jest, чтобы протестировать функцию addNums. Первым аргументом будет имя этого конкретного теста, а вторым аргументом будут тестируемые ожидания. Этот метод говорит сам за себя, как на простом английском языке: ожидайте, что функция сложит числа 4 и 5, и результат будет равен 9. Вторая строка теста предназначена для проверки прохождения 4 и 5 не должна давать результат 10. Легко.

Чтобы запустить этот тест, нам нужно настроить запуск «тестового» сценария в package.json. Вы можете настроить его следующим образом:

"scripts": {
    "test": "jest ./*test.js"
  }

Это говорит Node запустить тест и поймать регулярное выражение имен файлов. После того, как вы изменили это, запустите:

npm test

Вы должны получить такой вывод:

PASS  ./script1.test.js

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.125 s
Ran all test suites matching /.\\*test.js/i.

Это означает, что теперь у вас есть один набор тестов (script1.test.js) и один тест (одно «это» — один тест).

Если вы не хотите вводить *npm test* каждый раз для запуска тестов, вы можете настроить тестовый скрипт в package.json, как показано ниже:

"scripts": {
    "test": "jest --watch ./*test.js"
  }

Каждый раз, когда вы сохраняете файл после внесения изменений, тест npm будет отслеживать и автоматически запускать тесты.

Давайте посмотрим на второй пример:

script2.js

Учитывая базу данных (массив JS) и поисковый запрос, верните имена, соответствующие этому термину (только первые 3 совпадения). Причина, по которой мы вводим db в качестве зависимости для этой функции, заключается в том, что эту функцию можно использовать повторно и ее легче тестировать с помощью фиктивной базы данных.
Функция «functionNotTested» не служит никакой цели, а просто показывает вам тестовые покрытия позже. Мы не собираемся писать тест для этой функции.

Кажется, в этой функции есть еще что проверить. Во-первых, мы можем проверить, возвращает ли функция ожидаемые результаты поиска с предоставленным условием поиска. Во-вторых, мы ожидаем, что функция вернет только первые 3 совпадения поискового запроса. Мы также можем проверить, передается ли null или undefined в функцию для условия поиска в качестве параметра, функция может правильно обработать его и вернуть пустой массив. Наконец, мы также можем убедиться, что эта функция поиска чувствительна к регистру. Нам не нужно выполнять реальное подключение к базе данных, так как это модульный тест. Прежде чем тестировать интеграцию с реальной базой данных, мы должны убедиться, что эта функция должна работать с введенным массивом БД и условием поиска, как и ожидалось. Следовательно, мы можем просто создать фиктивный массив БД и передать его в функцию (вот вам и преимущество написания повторно используемого кода). И это тестовый сценарий, который мы можем построить:

Это должно иметь для вас общий смысл. Если функция встречает критерий поиска, который не существует, или получает null или undefined в качестве условия поиска, функция должна вернуть пустой массив (это обрабатывает функция «фильтр» JavaScript). В последнем тесте мы ожидаем, что функция поиска будет чувствительна к регистру, и поэтому такие имена, как «Лили…» и «… Ли», не должны появляться в результатах. Наконец, функция «описать» используется для группировки нескольких тестов в единое целое. Поэтому, когда результаты будут распечатаны, эти тесты будут иметь имя группы под названием «Функция, которая находит имена, совпадающие с поисковым запросом в базе данных». «toEqual» можно использовать для тестирования объектов JavaScript.

Давайте рассмотрим последний пример:

script3.js

Нам нужно будет вызывать API в третьем скрипте, так как мы используем Node.js (а API выборки браузера недоступен), вы можете установить isomorphic-fetch для Node.js:

npm install isomorphic-fetch

API, который мы используем в этом примере, — PokéAPI. Удобно получать информацию о покемонах, передавая покемонов, которых вы хотите найти, в путь к API. Эта функция возвращает имя, вес и рост найденного покемона.

А пока я хотел бы представить еще одну функциональность Jest: предоставление общего представления о покрытии тестами вашего кода.

После того, как вы создали «script3.js», запустите это:

npm test — — coverage

Вы должны увидеть это:

Это показывает, какой процент тестов был написан для покрытия каждого файла JavaScript и какая строка не покрыта. Помните, что в нашем script2.js была функция, для которой мы не писали никакого теста, и именно поэтому script2.js не получает 100%. Мы не написали ни одного тестового примера для script3.js, поэтому его покрытие тестами равно 0%.

Хорошо, мы можем начать писать тест для script3.js, давайте сначала попробуем этот тестовый скрипт:

Итак, что пытается сделать этот скрипт, так это то, что он пытается вызвать API и получить данные для сравнения с ожидаемыми значениями. Давайте попробуем запустить *npm test*:

> [email protected] test C:\Users\Dylan Oh\source\repos\jest-testing
> jest ./*test.js

 PASS  ./script2.test.js
 PASS  ./script3.test.js
 PASS  ./script1.test.js

Test Suites: 3 passed, 3 total                                                                                                                                                                                                   
Tests:       6 passed, 6 total
Snapshots:   0 total
Time:        0.801 s, estimated 1 s
Ran all test suites matching /.\\*test.js/i.

Ура! Это прошло! Или… это правда?

Что ж, есть способ узнать это. Мы можем добавить функцию для проверки того, сколько утверждений было передано в тесте:

expect.assertions(numberOfAssertionsExpected);

Давайте добавим это в наш script3.test.js:

Мы ожидаем, что здесь будет сделано 3 утверждения, для имени, веса и роста соответственно. Запустите тест npm:

FAIL  ./script3.test.js
  ● Find the Pokemon from PokeAPI and return its name, weight and height

    expect.assertions(3);

    Expected three assertions to be called but received zero assertion calls.

      3 |
      4 | it("Find the Pokemon from PokeAPI and return its name, weight and height", () => {
    > 5 |     expect.assertions(3);
        |            ^
      6 |     fetchPokemon("bulbasaur", fetch).then(data => {
      7 |         expect(data.name).toBe("bulbasaur");
      8 |         expect(data.height).toBe(7);

      at Object.<anonymous> (script3.test.js:5:12)

 PASS  ./script2.test.js
 PASS  ./script1.test.js

Test Suites: 1 failed, 2 passed, 3 total                                                                                                                                                                                         
Tests:       1 failed, 5 passed, 6 total
Snapshots:   0 total
Time:        0.842 s, estimated 1 s
Ran all test suites matching /.\\*test.js/i.
npm ERR! Test failed.  See above for more details.

Упс… вызов с нулевым утверждением. Так что же здесь происходит? Причина в том, что ассерты ничего не знают об асинхронном вызове, а до получения данных тесты уже пройдены. Следовательно, нам нужен способ сказать этим утверждениям, чтобы они ждали, пока не вернутся данные.

Один из способов решить эту проблему — передать «готовую» функцию в функцию обратного вызова тестового метода и поместить ее после утверждений.

И он прошел и гарантировал, что было сделано три вызова утверждений.

PASS  ./script3.test.js
 PASS  ./script2.test.js
 PASS  ./script1.test.js

Test Suites: 3 passed, 3 total                                                                                                                                                                                                   
Tests:       6 passed, 6 total
Snapshots:   0 total
Time:        0.868 s, estimated 1 s
Ran all test suites matching /.\\*test.js/i.

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

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

Мы можем удалить ненужную функцию в script2.js и запустить

npm test -- --coverage

А там у нас 100% покрытие тестами.

Всегда рекомендуется писать тесты для своего кода, независимо от того, будут ли они тестироваться локально или в конвейере CI/CD. Это поможет нам раньше выявлять потенциальные ошибки и заставить себя писать более качественный код.

Следите за мной в Medium, Twitter или Linkedin, чтобы получать больше статей на темы веб-разработки, Web3 и других!