1Начните тестировать свой код JavaScript с помощью Jest
2Тестирование компонентов React
Когда более одного разработчика активно вносят изменения в кодовую базу, обычно возникают проблемы и ошибки. Также сложно определить, кто допустил ошибку в коде и какова их причина. Этого можно избежать, написав тесты. Отдельные разработчики могут протестировать ошибки локально. Автоматические наборы тестов также могут быть настроены в конвейерах CI/CD, которые запускаются при фиксации кода.
Еще одно преимущество написания тестов заключается в том, что когда мы разрабатываем функции для приложения, мы склонны писать более качественные и чистые функции, поскольку понимаем, что в конечном итоге нам придется писать для них тесты.
Модульный тест
Модульный тест используется для тестирования наименьшей единицы исходного кода (например, функций или методов). Это самый простой в реализации и самый распространенный тест среди всех типов.
Интеграционный тест
Он предназначен для проверки перекрестной связи между различными компонентами или модулями в базе кода, например, функции аутентификации, которые включают различные части архитектуры приложения. Интеграционные тесты строятся на основе отдельных модульных тестов.
Сквозное тестирование
Сквозное тестирование, как следует из названия, предназначено для проверки рабочего процесса программного обеспечения от начала до конца. Это может быть очень сложно, когда приложение становится больше, и поэтому многие компании все еще проводят ручное тестирование. Процесс может начаться с запуска браузера, ввода URL-адреса веб-приложения в адресной строке , который управляется пользовательским интерфейсом. Однако существуют также такие инструменты, как Selenium, Cypress и Protractor, помогающие автоматизировать сквозное тестирование, хотя для его настройки может потребоваться довольно много времени.
Существует довольно много библиотек тестирования, предназначенных для разных целей и для разных языков программирования. В этой статье мы сосредоточимся на тестировании кода JavaScript с помощью Джест.
Jest — популярная (особенно для библиотеки React) библиотека тестирования JavaScript.
Она предоставляет широкий спектр методов и функций, которые охватывают многие аспекты в процессе тестирования. Когда вы используете фреймворк create-react-app, Jest уже встроен в него. В сегодняшней статье мы рассмотрим простую настройку Jest для вашего JavaScript кода, а также как мы можем начать локальное тестирование функциональности нашего приложения.
Сначала мы инициализируем рабочий каталог с помощью npm.
1 2 3 4 |
npm init -y |
-y flags в основном означают автоматический прием подсказок от npm init (вместо нажатия клавиши ввода для каждой подсказки).
Далее мы устанавливаем Jest из npm. Нам нужно только установить Jest в качестве зависимостей для разработчиков, потому что это требуется только на этапе разработки.
1 2 3 4 |
npm install jest --save-dev |
После установки вы должны увидеть, что пакет Jest включен в devDependencies файла package.json.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
{ "name": "jest-testing", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "jest": "^27.4.5" } } |
Теперь давайте начнем с нашего первого примера:
script1.js
1 2 3 4 5 6 7 8 |
const addNums = (a, b) => { return a + b; }; module.exports = addNums; |
Сценарий 1 просто складывает два числа и возвращает сумму.
Чтобы протестировать script1.js, мы создаем еще один файл с именем «script1.test.js» (было бы хорошо следовать соглашению об именах тестовых файлов для скриптов). В этот тестовый скрипт мы можем добавить следующий код JavaScript:
1 2 3 4 5 6 7 8 9 |
const addNums = require('./script1'); it('Function that adds two numbers and return sum', () => { expect(addNums(4, 5)).toBe(9); expect(addNums(4, 5)).not.toBe(10); }); |
Это означает, что мы импортируем функцию addNums из script1.js и выполняем тест в этом сценарии. Вы можете написать «test» или его псевдоним «it» (который мы использовали в скрипте) из Jest, чтобы протестировать функцию addNums. Первым аргументом будет имя этого конкретного теста, а вторым аргументом будут тестируемые ожидания. Этот метод говорит сам за себя, как простой английский: ожидайте, что функция сложит числа 4 и 5, и результат будет 9. Вторая строка теста предназначена для проверки прохождения 4 и 5 не должна давать результат 10.
Чтобы запустить этот тест, нам нужно настроить «тестовый» скрипт в package.json для запуска. Вы можете настроить следующим образом:
1 2 3 4 |
npm test |
Вы должны получить такой вывод:
1 2 3 4 5 6 7 8 9 10 |
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, как показано ниже:
1 2 3 4 5 6 |
"scripts": { "test": "jest --watch ./*test.js" } |
Каждый раз, когда вы сохраняете файл после внесения изменений, npm test будет автоматически отслеживать и запускать тесты.
Давайте посмотрим на второй пример:
script2.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const findNames = (term, db) => { const matches = db.filter(names => { return names.includes(term); }); // We only want the first three of search results. return matches.length > 3 ? matches.slice(0, 3) : matches; } const functionNotTested = (term) => { return `Hello ${term}!`; }; module.exports = findNames; |
Учитывая базу данных (массив JS) и поисковый запрос, верните имена, соответствующие этому термину (только первые 3 совпадения). Причина, по которой мы вводим базу данных в качестве зависимости для этой функции, чтобы эту функцию можно было использовать повторно и ее было легче тестировать с фиктивной базой данных.
Функция «functionNotTested» не служит никакой цели, а просто показывает вам тестовые покрытия позже. Мы не будем писать тест для этой функции.
В этой функции есть еще что проверить. Во-первых, мы можем проверить, возвращает ли функция ожидаемые результаты поиска с предоставленным условием поиска. Во-вторых, мы ожидаем, что функция вернет только первые 3 совпадения поискового запроса. Мы также можем проверить, передается ли null или undefined в функцию для условия поиска в качестве параметра, функция может правильно обрабатывать и возвращать пустой массив. Наконец, мы также можем убедиться, что эта функция поиска чувствительна к регистру. Нам не нужно выполнять реальное подключение к базе данных, так как это модульный тест. Прежде чем тестировать интеграцию с реальной базой данных, мы должны убедиться, что эта функция должна работать с внедренным массивом БД и условием поиска, как и ожидалось. Следовательно, мы можем просто создать фиктивный массив БД и передать его в функцию (вот вам и преимущество написания повторно используемого кода).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
const findNames = require('./script2'); const mockDB = [ "Kamron Rhodes", "Angelina Frank", "Bailee Larsen", "Joel Merritt", "Mina Ho", "Lily Hodge", "Alisha Solomon", "Frank Ho", "Cassidy Holder", "Mina Norman", "Lily Blair", "Adalyn Strong", "Lily Norman", "Minari Hiroko", "John Li", "May Li" ] describe("Function that finds the names which match the search term in database", () => { it("Expected search results", () => { // This should return empty array as "Dylan" does not exist in the mockDB expect(findNames("Dylan", mockDB)).toEqual([]); expect(findNames("Frank", mockDB)).toEqual(["Angelina Frank", "Frank Ho"]); }); it("This should handle null or undefined as input", () => { expect(findNames(undefined, mockDB)).toEqual([]); expect(findNames(null, mockDB)).toEqual([]); }); it("Should not return more than 3 matches", () => { expect(findNames('Li', mockDB).length).toEqual(3); }) it("The search is case sensitive", () => { expect(findNames('li', mockDB)).toEqual(["Angelina Frank", "Alisha Solomon"]) }) }) |
Это должно иметь для вас общий смысл. Если функция встречает условие поиска, которое не существует, или получает null или undefined в качестве условия поиска, функция должна возвращать пустой массив (это обрабатывает функция «фильтр» JavaScript). В последнем тесте мы ожидаем, что функция поиска чувствительна к регистру, и поэтому такие имена, как «Лили…» и «…Ли», не должны появляться в результатах. Наконец, функция «описать» используется для группировки нескольких тестов в единое целое. Поэтому, когда результаты будут распечатаны, эти тесты будут иметь имя группы под названием «Функция, которая находит имена, которые соответствуют поисковому запросу в базе данных». «toEqual» можно использовать для тестирования объектов JavaScript.
Давайте рассмотрим еще пример:
script3.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
const fetch = require('isomorphic-fetch'); const fetchPokemon = async (pokemon, fetch) => { const apiUrl = `https://pokeapi.co/api/v2/pokemon/${pokemon}`; const results = await fetch(apiUrl); const data = await results.json(); return { name: data.name, height: data.height, weight: data.weight }; }; module.exports = fetchPokemon; |
Нам нужно будет вызвать API в третьем скрипте, так как мы используем Node.js (а API-интерфейс браузера недоступен), вы можете установить isomorphic-fetch для Node.js:
1 2 3 4 |
npm install isomorphic-fetch |
API, который мы используем в этом примере, — PokéAPI. Удобно получать информацию о покемонах, передавая покемонов, которых вы хотите найти, в путь к API. Эта функция возвращает имя, вес и рост найденного покемона.
А пока мы хотели бы представить еще одну функциональность Jest: предоставление общего представления о покрытии тестами вашего кода.
После того, как вы создали «script3.js», запустите это:
1 2 3 4 |
npm test -- --coverage |
Вы должны увидеть это:
Это показывает, какой процент тестов был написан для покрытия каждого файла JavaScript и какая строка не покрыта. Помните, что в нашем script2.js была функция, для которой мы не писали никакого теста, и именно поэтому script2.js не получает 100%. Мы не написали ни одного тестового примера для script3.js, поэтому его покрытие тестами равно 0%.
Хорошо, мы можем начать писать тест для script3.js, давайте сначала попробуем этот тестовый скрипт:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const fetch = require('isomorphic-fetch'); const fetchPokemon = require('./script3'); it("Find the Pokemon from PokeAPI and return its name, weight and height", () => { fetchPokemon("bulbasaur", fetch).then(data => { expect(data.name).toBe("bulbasaur"); expect(data.height).toBe(7); expect(data.weight).toBe(69); }); }) |
Итак, что пытается сделать этот скрипт, так это то, что он пытается вызвать API и получить данные для сравнения с ожидаемыми значениями. Давайте попробуем запустить тест npm :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
> jest-testing@1.0.0 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. |
Все получилось!
Теперь мы можем проверить это. Мы можем добавить функцию для проверки того, сколько утверждений было передано в тесте:
1 2 3 4 |
expect.assertions(numberOfAssertionsExpected); |
Давайте добавим это в наш script3.test.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
const fetch = require('isomorphic-fetch'); const fetchPokemon = require('./script3'); it("Find the Pokemon from PokeAPI and return its name, weight and height", () => { expect.assertions(3); fetchPokemon("bulbasaur", fetch).then(data => { expect(data.name).toBe("bulbasaur"); expect(data.height).toBe(7); expect(data.weight).toBe(69); }); }) |
Мы ожидаем, что здесь будет сделано 3 утверждения, для имени, веса и роста соответственно. Запустите npm-тест :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
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. |
Упс… вызов с нулевым утверждением. Так что же здесь происходит? Причина в том, что ассерты ничего не знают об асинхронном вызове, а до получения данных тесты уже пройдены. Следовательно, нам нужен способ сказать этим утверждениям, чтобы они ждали, пока не вернутся данные.
Один из способов решить эту проблему — передать «готовую» функцию в функцию обратного вызова тестового метода и поместить ее после утверждений.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
const fetch = require('isomorphic-fetch'); const fetchPokemon = require('./script3'); it("Find the Pokemon from PokeAPI and return its name, weight and height", (done) => { expect.assertions(3); fetchPokemon("bulbasaur", fetch).then(data => { expect(data.name).toBe("bulbasaur"); expect(data.height).toBe(7); expect(data.weight).toBe(69); done(); }); }) |
И он прошел и гарантировал, что было сделано три вызова утверждений.
1 2 3 4 5 6 7 8 9 10 11 12 |
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 достаточно умен, чтобы дождаться возврата результатов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
const fetch = require('isomorphic-fetch'); const fetchPokemon = require('./script3'); it("Find the Pokemon from PokeAPI and return its name, weight and height", () => { expect.assertions(3) return fetchPokemon("bulbasaur", fetch).then(data => { expect(data.name).toBe("bulbasaur"); expect(data.height).toBe(7); expect(data.weight).toBe(69); }); }) |
Предлагаем использовать оператор return для возврата промиса и всегда помнить о том, что нужно включать количество вызовов утверждений, ожидаемых для тестирования асинхронной функции, чтобы убедиться, что утверждения действительно выполнялись.
Мы можем удалить ненужную функцию в script2.js и еще раз запустить npm test –coverage :
А там у нас 100% покрытие тестами.
Всегда рекомендуется писать тесты для своего кода, независимо от того, тестируются ли они локально или в конвейере CI/CD. Это поможет нам раньше выявлять потенциальные ошибки и заставить себя писать более качественный код.
Источник статьи: http://dev.to/