Webrium
Подписаться

С каких тестов начинать, если их нет совсем

Как превратить баги в тесты и защититься от их повторного появления

Схема стыка: тесты на баги окупаются, тесты на утилиты — нетТест на багТест на утилиту

Как выбрать первые тесты, когда их нет совсем

Тестирование — это не про то, чтобы покрыть код ради галочки. Это про то, чтобы заметить поломку до того, как её увидят пользователи. Но когда тестов нет вообще, первый вопрос не “как покрыть всё”, а “с чего начать, чтобы не потратить время впустую”.

Самый очевидный подход — начать с простого: покрыть тестами утилитарные функции, которые не зависят от внешних систем. Такие тесты пишутся быстро, они стабильны, и их легко мокать. Но это и самый бесполезный путь: утилиты редко ломаются так, чтобы это заметили сразу, а тесты на них создают иллюзию прогресса.

Тупик: начинать с утилит

Первое, что обычно делают — пишут тесты на функции без зависимостей. Например, на парсинг строк, форматирование данных или простые вычисления. Такие тесты действительно простые: они не требуют моков, не зависят от сети или базы данных, и их легко написать за пять минут.

Но у этого подхода есть фундаментальная проблема: тесты на стабильный код не окупаются. Если функция не меняется годами, тест на неё не упадёт никогда — если только её не перепишут целиком. А если её перепишут, тест всё равно придётся переписывать. Получается замкнутый круг: время на написание и поддержку теста тратится, но пользы от него нет.

Вторая проблема — тесты на утилиты не ловят реальные баги. Утилитарные функции редко ломаются сами по себе. Обычно они ломаются вместе с кодом, который их вызывает. Если функция форматирования строки сломается, это заметят только там, где её используют. А если она используется в критическом пути, там уже должен быть тест, который её покроет.

В итоге получается набор тестов, который выглядит внушительно, но не приносит реальной пользы. Это классический тупик: красивые цифры покрытия не спасают от простоев и потери данных.

Как определить приоритеты

Чтобы тесты приносили пользу, они должны покрывать код, который:

  • меняется достаточно часто, чтобы тест успел окупиться;
  • ломается так, что это заметно сразу;
  • можно протестировать без сложных моков и внешних зависимостей.

Для каждого кандидата на тестирование нужно ответить на три вопроса:

Как часто меняется этот код?

Если код стабилен, тест на него будет жить долго, но и пользы от него будет мало. Если код меняется часто, тест придётся поддерживать, но зато он будет ловить баги на ранней стадии.

Определить частоту изменений можно по истории коммитов. Если файл меняется раз в несколько месяцев, тест на него, скорее всего, не окупится. Если файл меняется каждую неделю, тест на него будет полезен.

Что будет, если этот код сломается?

Здесь важно оценить не только прямые убытки, но и косвенные затраты. Например:

  • Простой системы и потеря дохода
  • Потеря данных или их некорректное отображение
  • Время, которое уйдёт на разбор инцидента
  • Репутационные риски

Если баг приводит к простою, потере данных или долгому разбору, тест на него окупится быстро. Если баг заметят только через неделю и его починка займёт пять минут, тест на него — не приоритет.

Можно ли воспроизвести этот баг без внешних зависимостей?

Тест, который зависит от базы данных, сети или стороннего API, будет падать из-за таймаутов, недоступности сервисов и рассинхрона данных. Такие тесты нужны, но они должны быть отдельными от модульных тестов.

Если тест требует сложных моков или долгой подготовки данных, его поддержка будет стоить дорого. Лучше начать с тестов, которые можно написать быстро и которые не будут падать без причины.

Тест на найденную ошибку: самый эффективный подход

Вместо того чтобы планировать покрытие заранее, можно реагировать на то, что уже сломалось. Это называется “тест на найденную ошибку”:

  1. Находите баг в продакшене или на этапе разработки.
  2. Пишете тест, который воспроизводит этот баг.
  3. Чините баг.
  4. Убеждаетесь, что тест проходит.

Такой подход даёт несколько преимуществ:

Вы покрываете именно то, что ломается

Тесты на найденные ошибки проверяют реальные сценарии, а не гипотетические. Они ловят баги, которые уже проявились, а не те, которые “могут проявиться”.

Тесты получаются конкретными и полезными

Они проверяют не абстрактное поведение функции, а конкретный сценарий, который привёл к багу. Такой тест не будет падать без причины.

Вы не тратите время на тесты, которые никогда не упадут

Если баг уже проявился, значит, он может проявиться снова. Тест на него не будет лишним.

Кроме того, такой подход дисциплинирует: если вы нашли баг, но не можете написать на него тест, значит, вы не до конца разобрались в проблеме.

Что делает тест плохим

Хороший тест должен быть независимым от всего, кроме кода, который он проверяет. Если тест падает из-за внешних факторов, его перестанут читать — так же, как алерты, которые срабатывают каждый день.

Вот основные признаки плохого теста:

Зависимость от порядка запуска

Если тест проходит только когда запущен после другого теста, это не тест, а лотерея. Такие тесты сложно поддерживать, и они часто падают без причины.

Например, если первый тест создаёт данные в базе, а второй тест их использует, то второй тест упадёт, если запустить его отдельно. Это делает набор тестов ненадёжным.

Зависимость от времени

Если тест падает в полночь, в високосный год или в определённый день недели, он бесполезен. Время — это внешняя зависимость, и тест не должен от него зависеть.

Например, тест, который проверяет генерацию отчёта за текущий месяц, может упасть первого числа, если не учитывает переход на новый месяц.

Зависимость от сети

Если тест требует доступа к внешним API или базам данных, он будет падать из-за таймаутов и недоступности сервисов. Такие тесты нужны, но они должны быть отдельными от модульных тестов.

Например, тест, который проверяет отправку письма через сторонний сервис, будет падать каждый раз, когда сервис недоступен. Это делает тест ненадёжным.

Зависимость от чужих данных

Если тест зависит от данных, которые создаёт другой тест или внешняя система, он будет падать из-за рассинхрона. Такие тесты сложно поддерживать, и они часто падают без причины.

Например, если один тест создаёт пользователя, а другой тест ищет этого пользователя по имени, то второй тест упадёт, если первый тест не создаст пользователя или создаст его с другим именем.

Сколько это стоит потом

Тесты — это не разовая акция, а постоянная ответственность. Каждый тест, который вы пишете, придётся поддерживать: обновлять, когда меняется код, чинить, когда он падает, и удалять, когда он становится ненужным.

Если вы напишете много тестов, которые зависят от внешних факторов, вы потратите больше времени на их поддержку, чем на разработку. И в какой-то момент вы перестанете обращать на них внимание — потому что “они всегда падают”.

Поэтому лучше меньше тестов, но таких, которые:

  • покрывают реальные сценарии;
  • падают только когда что-то действительно сломалось;
  • легко поддерживаются.

Позиция редакции

Начинать с тестов нужно с того, что ломается чаще всего и дороже всего. Не с того, что проще всего покрыть. Не с утилит, которые никто не трогает.

Первый тест должен быть на тот код, поломка которого заметна сразу — потому что она приводит к простою, потере данных или долгому разбору. Такой тест окупится в первый же день.

Если вы не знаете, с чего начать, начните с тестов на найденные ошибки. Это самый дешёвый способ набрать полезный набор тестов — и самый надёжный способ убедиться, что они действительно проверяют то, что нужно.

Когда эта позиция неверна: Если у вас нет ни одного сбоя, который можно было бы покрыть тестом, или если вы работаете над новой системой, где ещё ничего не ломалось, — тогда начинайте с критического пути. Но даже в этом случае не гонитесь за количеством: лучше меньше тестов, но таких, которые действительно полезны.

С чем это связано

Метка над заголовком говорит, зачем туда идти: продолжить тему, перейти к практике или увидеть возражение.

Что проверяет браузерный тест, а что остаётся человекуавтоматомруками
Практика

Браузерные тесты: что автоматизировать, а что оставить руками

Как автоматизировать только нужное в браузерных тестах, чтобы не утонуть в сценариях и сохранить скорость команды.

Схема стыковки кода: совпадение ожиданий ревьюера и автора против разрываПонятный кодНепонимание
Усиление

Ревью кода: что смотреть и чего не писать в комментариях

Как грамотно ревьюить код, чтобы улучшить систему, а не просто искать баги — и почему это экономит время всей команде.

Два стыка: при честном протоколе замера результат стенда совпадает с поведением продакшена, при дефолтном прогоне — расходится с нимчестный протоколдефолтный прогон
Углубление

Как мерить производительность, чтобы себе не соврать

Как избежать ложных выводов при бенчмаркинге: от модели нагрузки до протокола замеров, чтобы результаты совпадали с реальностью.

Письмо по вторникам

Один разбор недели и короткий список того, что изменилось. Без дайджестов на сорок ссылок.

Начните вводить — материалы появятся здесь.

выбрать · Enter открыть · Esc закрыть