Юнит-тест проверяет небольшой самостоятельный фрагмент программы: функцию, метод или правило. Он запускается быстро и сообщает, сохраняет ли код ожидаемое поведение после изменений. Для новичка это способ перестать проверять всё вручную и получить уверенность при рефакторинге.
Разберём, из чего состоит хороший юнит-тест, что стоит проверять в первую очередь и как не превратить тестовый набор в ещё один сложный проект.
Что такое юнит-тест
Юнит, или модуль, — минимальная часть программы, которую удобно проверить отдельно. Размер зависит от языка и архитектуры. В одном проекте это чистая функция расчёта скидки, в другом — метод класса, принимающий входные данные и возвращающий результат.
Юнит-тест задаёт исходные условия, вызывает код и сравнивает фактический результат с ожидаемым. Если они не совпали, тест завершается ошибкой. Такой сигнал особенно полезен после изменений в общей функции, которую вызывают разные части приложения.
Чем юнит-тест отличается от других проверок
| Вид теста | Что проверяет | Скорость | Типичная причина сбоя |
|---|---|---|---|
| Юнит-тест | Один модуль в изоляции | Высокая | Ошибка в локальном правиле или функции |
| Интеграционный | Работу нескольких компонентов вместе | Средняя | Неверный контракт, конфигурация или обмен данными |
| Системный или end-to-end | Путь пользователя через приложение | Низкая | Сбой в любой части полного сценария |
Виды тестов не заменяют друг друга. Юнит-тест не докажет, что форма отправляет данные в реальную базу, а end-to-end проверка не всегда покажет, какая функция дала неверный результат.
Из чего состоит тест
Удобная модель называется Arrange, Act, Assert.
- Arrange. Подготовьте входные данные и зависимости.
- Act. Вызовите проверяемую функцию или метод.
- Assert. Сравните результат с ожидаемым значением.
Пример на условном JavaScript:
function finalPrice(price, discount) {
return price - price * discount;
}
test('уменьшает цену на 10 процентов', () => {
const result = finalPrice(1000, 0.1);
expect(result).toBe(900);
});
Здесь тест сообщает не внутреннее устройство функции, а ожидаемое поведение. Если реализация изменится, но результат останется правильным, проверку не придётся переписывать.

Что проверять первым
Не начинайте с цели «покрыть весь проект». Выберите код, где ошибка имеет понятные последствия и результат легко проверить.
- Расчёты цены, скидки, комиссии и налогов.
- Проверка формата данных.
- Преобразование дат, строк и коллекций.
- Права доступа и бизнес-правила.
- Исправленная ошибка, которая может повториться.
- Функции с несколькими ветками условий.
Сложный код часто легче тестировать после небольшого упрощения. Принципы понятных функций и имён разобраны в статье о чистом коде.
Проверяйте нормальный случай и границы
Один счастливый сценарий даёт ложное чувство безопасности. Для функции скидки важны нулевая скидка, максимальное разрешённое значение, отрицательная цена, дробные числа и неверный тип данных. Набор зависит от контракта функции.
Полезная последовательность:
- Обычный корректный ввод.
- Минимальное и максимальное допустимое значение.
- Пустое значение, если оно возможно.
- Неверный формат.
- Известный пример ошибки из прошлого.
Не придумывайте поведение за код. Сначала определите контракт: функция возвращает значение, выдаёт ошибку или использует значение по умолчанию.
Один тест и одна причина падения
Тест должен помогать диагностике. Если в одном сценарии проверяются расчёт, запись в базу, отправка письма и формат страницы, по сообщению об ошибке трудно понять источник. Разделите проверку на несколько уровней и дайте каждому тесту ясное имя.
Хорошее имя описывает условие и результат: «возвращает ноль для пустой корзины» или «не разрешает скидку выше установленного лимита». Название «test1» ничего не объясняет.
Изоляция зависимостей
Сеть, база данных, текущее время и случайные числа делают тест медленным и нестабильным. В юнит-тесте такие зависимости обычно заменяют управляемыми объектами, заглушками или функциями. Тогда проверка получает одни и те же условия при каждом запуске.
Изоляция не должна скрывать реальную интеграцию. Отдельно нужны тесты, которые подтверждают, что приложение умеет подключаться к базе и правильно работает с внешним API.

Что такое хороший юнит-тест
- Быстрый. Набор можно запускать после небольшого изменения.
- Независимый. Результат одного теста не зависит от порядка запуска.
- Повторяемый. Одинаковые условия дают одинаковый результат.
- Понятный. По имени и сообщению ясно, какое правило нарушено.
- Устойчивый к рефакторингу. Проверяется поведение, а не случайная деталь реализации.
Покрытие кода не равно качеству
Метрика покрытия показывает, какие строки или ветки выполнялись во время тестов. Она помогает найти слепые зоны, но высокий процент не доказывает правильность проверок. Тест может выполнить строку и не проверить результат.
Используйте покрытие как карту, а не как самоцель. Сначала защищайте критичные правила и места, которые часто меняются. Бессмысленные тесты ради числа усложняют поддержку.
Тестовые данные и фикстуры
Тестовые данные должны быть короткими и показывать только важное условие. Если для одного расчёта приходится создавать огромный объект пользователя, заказа и доставки, вынесите общую подготовку в небольшую фикстуру. При этом не скрывайте в ней значения, от которых зависит ожидаемый результат.
Не используйте случайные данные без фиксированного начального значения. Случайность затрудняет повторение ошибки. Лучше явно описать несколько характерных примеров и добавить генеративные проверки отдельно, когда команда понимает их пользу.
Когда тесты мешают
Хрупкие проверки привязаны к внутренней реализации. Они падают после безопасного переименования переменной или перестановки вспомогательных шагов. Другая крайность — огромные подготовительные блоки, в которых сложнее разобраться, чем в рабочем коде.
Если тест трудно написать, это может быть сигналом сильной связанности модуля. Не всегда нужно немедленно переделывать архитектуру, но проблему стоит зафиксировать как часть технического долга.
Как внедрить тесты в существующий проект
- Выберите тестовый фреймворк, принятый в экосистеме языка.
- Добавьте команду запуска в проект и документацию.
- Напишите тест на небольшой чистый модуль.
- Добавляйте тест при каждом исправлении ошибки.
- Запускайте набор перед коммитом и в системе непрерывной интеграции.
- Удаляйте или исправляйте нестабильные тесты, а не привыкайте к красному отчёту.
При знакомстве с большим репозиторием сначала найдите существующую структуру тестов и команды запуска. В этом поможет руководство о том, как читать чужой код.
Минимальный старт
Возьмите функцию с простым входом и результатом. Зафиксируйте один обычный сценарий, затем добавьте границу и ошибочный ввод. Запускайте тест после каждого изменения. Когда этот цикл станет привычным, переходите к зависимостям и интеграционным проверкам.
Юнит-тесты полезны не количеством, а скоростью обратной связи. Они должны помогать смело менять код и быстро понимать, какое правило нарушено.