Изученной информации уже достаточно для тестирования в повседневной практике разработки. Перед тем, как погружаться в более сложные темы и возможности Jest, пройдём полный путь тестирования библиотеки, поговорим об организации тестов, хороших и плохих практиках. Это поможет сформировать правильное отношение к тестированию в целом.
В этом уроке мы разберём основы модульного тестирования. Это тестирование направлено на проверку модулей программы в изоляции от всех остальных частей. Эти тесты обычно тестируют базовые конструкции языка: функции, модули, классы. Такие тесты не дают никаких гарантий работы всего приложения в целом, но хорошо помогают тогда, когда какой-то модуль программы имеет сложную логику.
Попробуем протестировать стек. Напомню, что стек представляет собой список элементов организованных по принципу LIFO. Данные кладутся в стек в одном порядке, а извлекаются в обратном. Сам стек, как правило, используется для реализации алгоритмов. Он часто используется в низкоуровневом коде: например, внутри языков программирования или в операционных системах.
import makeStack from '../src/stack.js';
const stack = makeStack();
stack.isEmpty(); // true
stack.push(1); // (1)
stack.push(2); // (1, 2)
stack.push(3); // (1, 2, 3)
stack.isEmpty(); // false
stack.pop(); // 3. В стеке (1, 2)
stack.pop(); // 2. В стеке (1)
stack.pop(); // 1. В стеке пусто
stack.isEmpty(); // true
Сначала решим организационные вопросы. Если предположить, что реализация стека лежит в файле src/stack.js, то его тест мы положим в файл __tests__/stack.test.js.
Теперь напишем первый тест. Первый тест всегда должен проверять позитивный сценарий — тот, в котором задействована основная функциональность тестируемого компонента:
import makeStack from '../src/stack.js';
test("stack's main flow", () => {
const stack = makeStack();
// Добавляем два элемента в стек и затем извлекаем их
stack.push('one');
stack.push('two');
expect(stack.pop()).toEqual('two');
expect(stack.pop()).toEqual('one');
});
Этот тест проверяет, что правильно работают два основных метода без учёта пограничных случаев. Для этого внутри теста выполняются два матчера, которые по очереди проверяют извлекаемые значения из стека.
В интернете можно встретить мнение, что несколько проверок в рамках одного теста это неправильно. Что тесты нужно детализировать максимально подробно и создавать новый тест на каждую проверку.
test("stack's main flow", () => {
const stack = makeStack();
stack.push('one');
stack.push('two');
expect(stack.pop()).toEqual('two');
});
test("stack's main flow", () => {
const stack = makeStack();
stack.push('one');
stack.push('two');
stack.pop();
expect(stack.pop()).toEqual('one');
});
Такой подход нередко приводит к серьёзному раздуванию кода и дублированию. А выгода не очевидна. Что по-настоящему надо выделять в отдельный тест, так это другой сценарий, которому нужны другие данные и который выполняет другую последовательность действий.
Следующим тестом будет тест на дополнительные функции стека. К таким у нас относится функция isEmpty()
, которая проверяет, пустой ли стек:
test('isEmpty', () => {
const stack = makeStack();
expect(stack.isEmpty()).toBe(true);
stack.push('two');
expect(stack.isEmpty()).toBe(false);
stack.pop();
expect(stack.isEmpty()).toBe(true);
});
В этом тесте проверяются сразу три ситуации:
В принципе, этого достаточно. Хотя в теории возможны ситуации, при которых isEmpty()
всё равно сломается. Нужно ли пытаться найти все варианты? Не нужно. Тесты не даются бесплатно, каждая написанная строчка кода в проекте — потенциальное место для изменения в случае правок. Если есть сомнения, нужно ли писать проверку или нет, то лучше не пишите. Так вы поймёте тот минимум, который стоит писать, и после которого тесты писать не эффективно. Редкие ситуации требуют покрытия тестами только тогда, когда они критичны для работоспособности.
Ну, и последнее, что можно протестировать — поведение функции pop()
, когда в стеке нет ни одного элемента. По задумке, стек выбрасывает исключение, если из него попытались взять элемент, когда тот был пустой. То есть эта ситуация рассматривается как ошибочная, программист всегда должен убеждаться в том что стек не пустой.
test('pop in empty stack', () => {
const stack = makeStack();
// Вызов метода pop обёрнут в функцию
// иначе матчер не сможет перехватить исключение
expect(() => stack.pop()).toThrow();
});
https://repl.it/@hexlet/js-testing-unit-tests-stack#tests/stack.test.js
Но не всегда пограничные случаи так легко увидеть. Маловероятно, что любой программист сможет сразу написать все нужные тесты. Если в коде возникла ошибка, для которой не было теста, то сначала напишите тест, который воспроизводит эту ошибку, и затем уже чините её. Только так можно поддерживать достаточный уровень надёжности, не превращая разработку в непрерывную починку багов.
Вам ответят команда поддержки Хекслета или другие студенты.
Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.
Загляните в раздел «Обсуждение»:
Статья «Ловушки обучения»
Вебинар «Как самостоятельно учиться»
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.
Наши выпускники работают в компаниях:
С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.
Зарегистрируйтесь или войдите в свой аккаунт