Jest tests: тестирование в JavaScript. Инструкция для начинающих
Jest — это популярный фреймворк для тестирования JavaScript с акцентом на простоту использования. Редакция Highload разобралась, как устроены тесты в Jest и как начать тестировать JavaScript-код начинающим.
Содержание:
Что такое тестирование?
Что такое Jest?
Установка и настройка Jest
Тестирование
Сопоставление
Подготовка нескольких тестов
Итоги
Что такое тестирование?
Тестирование — это процесс проверки работоспособности кода в различных условиях и на разных уровнях.
Процесс тестирования состоит из нескольких этапов. Обычно этапы тестирования представляют в виде пирамиды из трех уровней: модульного, интеграционного и сквозного (E2E, end-to-end) тестирования.
Ниже приведена пирамида с более подробным разделением на этапы. В ней отражены следующие этапы тестирования: на уровне отдельных модулей, интеграции группы модулей, приемки, после внесения изменений и при финальной обкатке.
Что такое Jest?
Jest — это среда тестирования JavaScript, которая создана с мыслью о простоте.
Она поддерживает не только JavaScript сам по себе, но и проекты с применением Babel, TypeScript, Node.js, React, Angular, Vue и пр.
Что нужно тестировать?
Jest используется для модульного тестирования.
Модульное тестирование (unit testing, юнит-тестирование) — это тестирование небольших блоков кода по мере их написания.
Поиск ошибки в крупном фрагменте кода отнимает много времени. Поэтому возникла концепция «сначала тест» (test-first). Ее суть заключается в том, что сначала пишется тест, а уже потом — тестируемый код. Впоследствии эта концепция выделилась в методологию разработки через тестирование. Благодаря применению такой процедуры вы можете убедиться в том, что код работает, как требуется.
Тестируемой единицей может быть как отдельная функция или метод, так и модуль в целом. Для тестирования выбирают самостоятельно написанные блоки (при написании кода с нуля) или блоки имеющегося кода, для которых не написаны тесты (при работе с существующей системой).
В некоторых случаях не обязательно создавать модульные тесты. Например, если вы создаете небольшой сайт из нескольких страниц, несложную игру или проект для выставки, который будет работать пару дней, а сроки поджимают, то есть возможность и смысл провести тестирование вручную.
Установка и настройка Jest
Установить Jest проще простого. Создайте каталог для тестового проекта, например jest_test
:
cd d:\ mkdir jest_test cd jest_test
Инициируйте npm
с ответами по умолчанию:
npm init -y
Будет создан файл package.json
с таким содержимым:
{ "name": "jest_test", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
Установите Jest с помощью npm
:
npm install --save-dev jest
Рекомендуется использовать именно такой способ установки, а не устанавливать Jest глобально.
Откройте файл package.json
и измените секцию scripts
:
"scripts": { "test": "jest" },
Теперь можно приступать к тестированию.
Тестирование
Рассмотрим этапы юнит-тестирования на примере простой функции возведения в квадрат.
1. Создать тест
Создадим файл square.test.js
со следующим кодом:
// Импортируем функцию const square = require('./square'); // Тест test('Возводит 2 в квадрат, чтобы вернуть 4', () => { expect(square(2)).toBe(4); });
2. Запустить тест (провал)
npm run test
Сначала тест не будет пройден и вы получите сообщения об ошибках, потому что тестируемая функция еще не реализована.
Именно так и принято делать: первое тестирование проводится до написания кода, и оно должно провалиться, чтобы мы убедились в том, что тест срабатывает при отсутствии кода.
3. Написать код
Напишем код для тестирования и сохраним его в файле square.js
.
// Объявляем функцию function square(x){ return x * x; } // Экспортируем функцию module.exports = square;
4. Запустить тест (успех)
npm run test
На этот раз тест будет пройден.
Сопоставление
Теперь функция определена, и мы проверяем, как она работает, с помощью сопоставления. Сопоставление производится с помощью функции expect()
и вычислителя. В данном случае это проверка на точное равенство toBe()
:
expect(square(2)).toBe(4);
Равенство
toBe
Этот вычислитель используется для сравнения примитивных типов или проверки идентичности экземпляров объекта по ссылке.
Для проверки он использует Object.is
. В случае примитивных значений это лучше, чем оператор строгого равенства ===
.
Обратите внимание, что не следует использовать toBe для чисел с плавающей точкой. Из-за округления 2.0 + 3.0 не обязательно будет равно 5.0. Для таких чисел лучше использовать toBeCloseTo
.
toBe
используется для тестирования точного совпадения. Если нужно проверить значение объекта, используйте сопоставитель toEqual
.
toEqual
Рассмотрим такой тест, чтобы сравнить toBe
и toEqual
:
function Cat (name, age) { this.name = name; this.age = age; }; var myCat = new Cat('Петька', 5) var herCat = new Cat('Венька', 3) // Группируем тесты describe('Кот', () => { test('Один и тот же кот - toBe:', () => { expect(myCat).toBe(myCat); }); test('Один и тот же кот - toEqual:', () => { expect(myCat).toEqual(myCat); }); test('Два разных кота с разными данными - toBe:', () => { expect(herCat).toBe(myCat); }); test('Два разных кота с разными данными - toEqual:', () => { expect(herCat).toEqual(myCat); }); test('Два разных кота с одинаковыми данными - toBe:', () => { expect({name: 'Петька', age: 5}).toBe(myCat); }); test('Два разных кота с одинаковыми данными - toEqual:', () => { expect({name: 'Петька', age: 5}).toEqual(myCat); }); });
После запуска тестов получим такой ответ:
Первая пара тестов пройдена, потому что объект сравнивается с самим собой. Он тождествен себе и по ссылке и по значениям.
Вторая пара тестов не пройдена, потому что сравниваются два разных объекта с разными значениями.
В третьей паре объявленный объект myCat
сравнивается с новым объектом {name: 'Петька', age: 5}
. Поскольку это разные объекты, тест toBe
провален, а поскольку значения параметров у этих объектов равны, тест toEqual
пройден.
toStrictEqual
Используется для тестирования объектов с одинаковыми типами и структурой.
В отличие от toEqual
, проверяет ключи со значением undefined
и проверяет массивы на разреженность.
Сравните результаты нестрогого и строгого сравнения:
test('Undefined equal', () => { expect({x: undefined, y: 'second'}).toEqual({y: 'second'}); }); test('Undefined strict equal', () => { expect({x: undefined, y: 'second'}).toStrictEqual({y: 'second'}); });
Отрицание
not
Когда требуется инвертировать условие, используется not
. Например, так можно провести тест и убедиться, что строка не в верхнем регистре.
str = 'строка'; test('неравенство строк в разных регистрах', () => { expect(str).not.toEqual(str.toUpperCase()); });
Сравнение чисел
Числа можно сравнивать не только на равенство, но и на неравенство. Есть функции для определения, является ли число большим/меньшим, чем другое число, и нестрогие пары этих функций.
test('3 + 2', () => { const val = 3 + 2; expect(val).toBeGreaterThan(3); expect(val).toBeGreaterThanOrEqual(5); expect(val).toBeLessThan(10); expect(val).toBeLessThanOrEqual(6); });
Обратите внимание, что несмотря на четыре проверки, мы видим, что выполнен только один тест. Так произошло потому, что все проверки помещены в один блок теста.
Истинность, нуль и определенность
Булевская ложность в JavaScript представлена значениями false
, 0
, ''
, null
, undefined
и NaN
. Все остальные значения с этой точки зрения трактуются как истинные. Но иногда во время тестирования нужно не только определять истинность значения, но и различать false
, null
и undefined
и NaN
.
toBeTruthy
Используется, когда вам важно не значение, а только его истинность в булевском контексте.
Тест будет пройден при любом значении, кроме false
, 0
, ''
, null
, undefined
и NaN
.
toBeFalsy
Используется, когда вам важно не значение, а только его ложность в булевском контексте.
Тест будет пройден только при одном из следующих значений: false
, 0
, ''
, null
, undefined
и NaN
.
toBeNull
Тест будет пройден только при значении null
.
toBeDefined
Используется, чтобы узнать, определено ли значение.
toBeUndefined
Используется, чтобы узнать, является ли значение неопределенным, например когда функция выбросила исключительную ситуацию.
toBeNaN
Определяет, является ли значение NaN
.
Пример
Рассмотрим тест, в котором применяются функции определения различных рассмотренных значений применительно к значению undefined
.
var v = undefined; describe('Истинность, нуль и определенность', () => { test('Expect undefined not.toBeTruthy', () => { expect(v).not.toBeTruthy(); }); test('Expect undefined toBeFalsy', () => { expect(v).toBeFalsy(); }); test('Expect undefined not.toBeNull', () => { expect(v).not.toBeNull(); }); test('Expect undefined not.toBeDefined', () => { expect(v).not.toBeDefined(); }); test('Expect undefined toBeUndefined', () => { expect(v).toBeUndefined(); }); test('Expect undefined not.toBeNaN', () => { expect(v).not.toBeNaN(); }); });
Вхождение в массив
toContain
Проверяет, содержится ли указанный элемент в массиве. Для проверки используется строгое равенство, как в toBe
.
toContainEqual
Проверяет, содержится ли в массиве элемент с определенной структурой и значениями. Проверяется равенство значений, а не эквивалентность объектов.
Чтобы наглядно продемонстрировать разницу между двумя этими функциями, запустим приведенный ниже комплект тестов.
var winter = ['декабрь', 'январь', 'февраль']; var spring = ['март', 'апрель', 'май']; var summer = ['июнь', 'июль', 'август']; var autumn = ['сентябрь', 'октябрь', 'ноябрь']; var seasons = [winter, spring, summer, autumn]; describe('Вхождение элементов в массив', () => { test('Есть ли среди сезонов весна?', () => { expect(seasons).toContain(spring); }); test("Есть ли среди сезонов ['март', 'апрель', 'май']?", () => { expect(seasons).toContain(['март', 'апрель', 'май']); }); test('Есть ли среди сезонов весна (Equal)?', () => { expect(seasons).toContainEqual(spring); }); test("Есть ли среди сезонов ['март', 'апрель', 'май'] (Equal)?", () => { expect(seasons).toContainEqual(['март', 'апрель', 'май']); }); });
Не пройден лишь один тест — второй: два различных объекта сравнивались по ссылке.
Сравнение со строками и регулярными выражениями
toMatch
Определяет, содержит ли аргумент указанную строку или соответствует ли он заданному регулярному выражению.
test('pineapple contains apple', () => { expect('pineapple').toMatch('apple'); }); test('pineapple contains pine', () => { expect('pineapple').toMatch(/pine/); });
Подготовка нескольких тестов
Иногда требуется провести одинаковую подготовку перед выполнением нескольких тестов. Так бывает, в частности, когда невозможно жестко закодировать данные и нужно получать их асинхронно. Jest предоставляет для этого функции beforeEach
и afterEach
.
Например, имеется база данных с продуктами, которую нужно обновлять перед каждым тестом и очищать после каждого из них.
Мы можем написать такой код:
beforeEach(() => { initProductDatabase(); }); afterEach(() => { clearProductDatabase(); }); test('Среди продуктов есть яблоко', () => { expect(getProducts()).toContainEqual('яблоко'); }); test('Среди продуктов есть помидор', () => { expect(getProducts()).toContainEqual('помидор'); });
Тогда в определенный момент в базе может найтись яблоко, но не окажется помидора, и мы получим следующий результат:
Если же нужно проводить подготовку перед всеми тестами и проводить дополнительную работу последних, применяются функции
beforeAll
и afterAll
.
В этом случае в приведенном выше фрагменте кода можно заменить beforeEach
на beforeAll
, а afterEach
— на afterAll
.
Бывает, что нужно провести некоторые подготовительные действия для всех тестов, а некоторые — только для отдельных. В таком случае функции подготовки можно включать в блок describe
. При этом функции before...
, находящиеся вне блока describe, будут выполняться раньше, чем те, которые в него включены.
Итоги
Написание тестов требует времени, но это время окупается при правильном подходе к тестированию. Фреймворк Jest предоставляет удобные средства для тестирования кода на JavaScript, а также является простым и понятным.
В этой статье мы рассмотрели только основы тестирования с использованием Jest. Однако он предоставляет далеко не только рассмотренные средства. Мы не рассказали о тестировании асинхронного кода, использовании снимков для тестирования и многих других возможностях фреймворка. На сайте Jest вы можете найти более подробную информацию, описание API и полезные ссылки, которые позволят вам поближе познакомиться с этим фреймворком.
В заключении, в качестве хорошего дополнения к материалу советуем посмотреть вот это тематическое видео:
Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: