Вы запускаете тесты — всё зелёное. Коммит, деплой. Через час приходит сообщение: «всё упало». Знакомо? Если да, то вы не одиноки. Юнит-тесты часто создают иллюзию безопасности. Но они проверяют лишь то, что написали вы, а не то, что нужно пользователю. Как так выходит и что с этим делать — разберём по полочкам.
Зелёный тест — это не гарантия корректности: как такое возможно
Тест зелёный, когда код делает то, что ожидает тест. А ожидание теста — это лишь ваше представление о правильном поведении. Если вы неверно поняли задачу, тест закрепит ошибку. Хуже того, тест может быть бессмысленным: проверяет сеттер, который просто присваивает значение, или метод, который возвращает константу. Формально всё зелёное, а по сути ничего не проверяет.
Ещё момент: зелёный тест говорит о том, что функция отработала без исключения и вернула ожидаемое значение на конкретных входных данных. Но что происходит при других данных? А при реальной нагрузке? А когда база данных недоступна? Тесты молчат.
Ошибка 1: тестируем только счастливый путь
Самый распространённый паттерн — когда тест вызывает метод с валидными параметрами и ожидает правильный результат. Всё хорошо, но ровно до тех пор, пока пользователь не введёт пустую строку, не отправит запрос без токена или не передаст отрицательное количество товаров.
Что делать? Добавляйте тесты на ошибочные сценарии: исключения, коды ответов, частичные данные. Пусть каждый метод имеет хотя бы один тест «на отказ». Это удвоит количество тестов, зато приложение перестанет падать в самый неподходящий момент.
Типичная отговорка: «это обработает валидация на фронте». Не факт. Валидация на фронте — это вежливость, а не защита. API должен выживать, когда с него снимают все ограничения.
Ошибка 2: игнорируем граничные значения и невалидные данные
Граница — это место, где код чаще всего ломается. Число 0, пустой список, строка максимальной длины, значение на границе допустимого диапазона. Если тест не проверяет границы, он пропустит классическое off-by-one и переполнение буфера.
Народная мудрость гласит: «Все баги живут на границах». Поэтому в чек-лист каждого теста включайте:
- минимальное и максимальное значение;
- значение чуть меньше и чуть больше границы;
- пустые и null-значения;
- неожиданные типы данных (например, строку вместо числа).
Это скучно, но именно такие тесты отлавливают большинство реальных падений.
Ошибка 3: моки отрывают тест от реальности
Моки — это двойники реальных зависимостей. Они полезны, когда надо изолировать логику. Но если замокать всё, что движется, тест начнёт проверять не ваш код, а ваши же насмешки. Мок не знает, как реально ведёт себя база данных, веб-сервис или файловая система. Он возвращает то, что вы ему сказали. А в проде всё иначе — отсюда сюрпризы.
Правильный подход: мокать надо только внешние границы (платёжный шлюз, почтовый сервис). Внутренние модули лучше тестировать вместе, беря реальные реализации. Это уже не юнит-тесты, а интеграционные, но они честнее.
Ошибка 4: интеграционные тесты забыты
Юнит-тесты проверяют каждый модуль по отдельности. Но даже если каждый модуль работает, вместе они могут развалиться. Например, вы изменили формат даты в одном сервисе, а другой всё ещё ждёт старый. Юнит-тесты этого не увидят. Интеграционные — увидят.
Интеграционные тесты гоняют код через реальные взаимодействия: базу данных, файловую систему, соседние сервисы. Их писать дольше, они медленнее, но именно они ловят проблемы, которые не видны по отдельности.
Если у вас вообще нет интеграционных тестов — вот причина, почему приложение падает. Не надо покрывать ими всё подряд. Достаточно критических путей: авторизация, оформление заказа, запись в базу.
Ошибка 5: нет тестов на производительность и масштабирование
Код может быть функционально правильным, но падать под нагрузкой: утечка памяти, блокировки, нехватка соединений с базой. Юнит-тесты этого не покажут. Нужны нагрузочные тесты, которые проверяют время ответа, потребление памяти, число одновременных запросов.
Если у вас нет нагрузочных тестов, вы летите вслепую. Сделайте хотя бы простой скрипт, который дёргает эндпоинт сотней параллельных запросов и смотрит, не упал ли сервер. Это уже лучше, чем ничего.
Как построить пирамиду тестов, которая не подведёт
Классическая пирамида тестов существует не для красоты. Она предлагает соотношение: много быстрых юнит-тестов внизу, меньше интеграционных в середине, ещё меньше end-to-end наверху. Но главное не пропорции, а то, что каждый уровень проверяет своё.
Юнит-тесты — это про логику и алгоритмы. Интеграционные — про взаимодействие компонентов. End-to-end — про сценарий пользователя от начала до конца. Производительность и граничные значения должны сидеть на каждом уровне, а не только на одном.
| Тип теста | Что проверяет | Скорость | Когда нужен |
|---|---|---|---|
| Юнит | Отдельный модуль | Очень быстрый | Почти всегда |
| Интеграционный | Связку модулей | Средний | Критические пути |
| E2E | Пользовательский сценарий | Медленный | Основные флоу |
| Нагрузочный | Производительность | Долгий | Сервисы под нагрузкой |
Если ваша пирамида перевёрнута или состоит из одних юнит-тестов, не удивляйтесь падениям. Начните с малого: добавьте по паре интеграционных тестов на самые важные сценарии. Потом — нагрузочный тест на самый горячий эндпоинт. И всегда помните: зелёный тест — это не гарантия, а лишь сигнал, что в этот раз всё сошлось.
Комментарии (0)
Войдите, чтобы комментировать.
Пока нет комментариев. Будьте первым.