Различные виды тестирования
- Модульные тесты (Unit tests)
Модульное тестирование, или юнит-тестирование (Unit testing) - процесс в программировании, позволяющий проверить на корректность отдельные модули исходного кода программы.
Идея состоит в том, чтобы писать тесты для каждой нетривиальной функции или метода. Это позволяет достаточно быстро проверить, не привело ли очередное изменение кода к регрессии, то есть к появлению ошибок в уже оттестированных местах программы, а также облегчает обнаружение и устранение таких ошибок.
- Интеграционные тесты (Integration tests)
В какой-то момент ваш код связывается с базой данных, файловой системой или другой третьей стороной. Это может быть даже другой модуль в вашем приложении.
Эта часть реализации должна быть проверена интеграционными тестами. Обычно они имеют более сложную настройку, которая включает подготовку сред тестирования, инициализацию зависимостей и так далее.
- Функциональные тесты (Functional tests)
Модульные и интеграционные тесты дают вам уверенность в том, что ваше приложение работает. Функциональные тесты смотрят на приложение с точки зрения пользователя и проверяют, что система работает должным образом.
TDD & BDD
Что это вообще за буквы? И то, и другое - подходы к разработке, когда сначала пишутся тесты, а потом код.
*DD (*что-то* Driven Development) - разработка, основанная на чем-то.
TDD (Test Driven Development) - Разработка на основе тестов.
BDD (Behavior Driven Development) - Разработка на основе поведения.
BDD, на самом деле, является расширением TDD-подхода. Тем не менее, они предназначены для разных целей и для их реализации используются разные инструменты.
- В чем разница
- А как выглядит на примере
Давайте возьмем простую задачку. Нам нужно сделать форму, в которую мы вводим возраст котика и его вес, а в ответ получаем, сколько корма котик должен кушать в сутки.
Как подойти к этой задаче, используя TDD подход:
Как подойти к этой задаче, используя BDD подход:
Тестируем JavaScript-код с Jest
Jest - это простая и удобная среда тестирования. Она не требует дополнительных настроек, легка в понимании и использовании, имеет неплохую документацию. Кроме того, прекрасно подходит для проектов, в которых используются Node, Angular, Vue, React, Babel, TypeScript. Давайте посмотрим, как всё это выглядит на практике.
- Установка
Чтобы установить Jest, выполняем:
npm install --save-dev jest
Также после установки можно обновить секцию scripts вашего файла package.json:
"scripts" : { "test": "jest" }
Посредством такого простого вызова уже можно запускать тесты, хотя Jest всё равно потребует существование хотя бы одного теста.
Выполнить установку можно и глобально:
npm install jest -global
Далее можно использовать Jest уже из командной строки.
- Первый тест на Jest
Итак, создадим файл first.test.js, а потом напишем первый тест:
// first.test.js test('My first test', () => { expect(Math.max(1, 5, 10)).toBe(10); });
Теперь запустим тесты посредством npm run test или командой jest (при глобальной установке). После запуска вы увидите отчёт о прохождении тестов.
<b>PASS</b> ./first.test.js ✓ My first test (1 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 0.618 s, estimated 1 s
Что же, разберём код теста. У нас есть функция test, используемая для создания нового теста. Она принимает 3 аргумента (в примере у нас 2 аргумента). Первый - это строка с названием теста (jest отображает его в отчёте). Второй - это функция, содержащая логику теста. При необходимости используют и третий аргумент - таймаут. Он необязателен и задаётся в миллисекундах. Значение по умолчанию - 5 сек.
Следует добавить, что вместо test() мы можем применять it() - разницы нету, а it() просто является алиасам на функцию test().
Идём дальше. Внутри функции теста мы поначалу вызываем функцию expect(), которой передаём значение для проверки. В нашем случае речь идёт о результате вызова Math.max(1, 5, 10). Здесь expect() возвратит объект-"обёртку", у которой есть методы для сопоставления полученного значения и ожидаемого значения. Один из этих методов мы, как раз, и использовали - это метод toBe.
- Разберем основные из этих методов:
toBe() - подойдёт, если нужно сравнить примитивные значения либо проверить, является ли переданное значение ссылкой на тот объект, который указан как ожидаемое значение. Значения сравниваются посредством Object.is();
toEqual() - если нужно сравнить структуру более сложных типов. Метод выполнит сравнение всех полей переданного объекта с ожидаемым. Он проверит каждый элемент массива, сделав это рекурсивно по всей вложенности:
test('toEqual with objects', () => { expect({ foo: 'foo', subObject: { baz: 'baz' } }) .toEqual({ foo: 'foo', subObject: { baz: 'baz' } }); // ок expect({ foo: 'foo', subObject: { num: 0 } }) .toEqual({ foo: 'foo', subObject: { baz: 'baz' } }); // ошибка }); test('toEqual with arrays', () => { expect([11, 19, 5]).toEqual([11, 19, 5]); // ок expect([11, 19, 5]).toEqual([11, 19]); // ошибка });
toContain() - проверит, содержит ли массив либо итерируемый объект значение (применяется оператор ===):
const arr = ['apple', 'orange', 'banana']; expect(arr).toContain('banana'); expect(new Set(arr)).toContain('banana'); expect('apple, orange, banana').toContain('banana');
toContainEqual() - содержит ли массив элемент с ожидаемой структурой:
expect([{a: 1}, {b: 2}]).toContainEqual({a: 1});
toHaveLength() - соответствует ли свойство length у объекта ожидаемому:
expect([1, 2, 3, 4]).toHaveLength(4); expect('foo').toHaveLength(3); expect({ length: 1 }).toHaveLength(1);
toBeNull() - проверка на равенство с null;
toBeUndefined() - проверка на равенство с undefined;
toBeDefined() - противоположность toBeUndefined. Проверка, является ли значение !== undefined;
toBeTruthy() - проверка, соответствует ли значение true в булевом контексте;
toBeFalsy() - противоположность toBeTruthy(). Проверка, соответствует ли значение в булевом контексте false;
toBeGreaterThan() - проверка, больше ли числовое значение, чем ожидаемое;
toBeGreaterThanOrEqual() - проверка, что ожидаемое значение больше или равно ожидаемому;
toBeLessThan() и toBeLessThanOrEqual() - противоположности toBeGreaterThan() и toBeGreaterThanOrEqual();
toBeCloseTo() - метод удобен для чисел с плавающей запятой, когда не важна точность, и вы не желаете, чтобы ваш тест зависел от незначительной разницы в дроби. В качестве 2-го аргумента вы можете передать, до какого знака после запятой нужна точность при сравнении:
const num = 0.1 + 0.2; // 0.30000000000000004 expect(num).toBeCloseTo(0.3); expect(Math.PI).toBeCloseTo(3.14, 2);
toMatch() - проверка, соответствует ли строка регулярному выражению:
expect('Banana').toMatch(/Ba/);
toThrow() - подходит, если нужно проверить исключение. Метод может проверить сам факт ошибки либо выполнить проверку на выброс исключения определённого класса, а также по сообщению ошибки либо по соответствию сообщения регулярному выражению:
function funcWithError() { throw new Error('some error'); } expect(funcWithError).toThrow(); expect(funcWithError).toThrow(Error); expect(funcWithError).toThrow('some error'); expect(funcWithError).toThrow(/some/);
not - свойство, позволяющее выполнить проверку на неравенство. Оно предоставляет объект, имеющий все вышеперечисленные методы, однако работать они будут наоборот:
expect(true).not.toBe(false); expect({ foo: 'bar' }).not.toEqual({}); function funcWithoutError() {} expect(funcWithoutError).not.toThrow();