Зачем тест, когда код пишется за минуту
Тесты — та часть ремесла, у которой хуже всего с репутацией. Их считают дисциплиной ради дисциплины, обузой, тем, что делают в правильных проектах и не делают в реальных. С появлением агента к этому добавился новый довод: зачем тест, если код пишется за минуту и переписать его тоже минута.
Довод выглядит убедительно и разбивается ровно об одну таблицу.
Раньше тест окупался тем, что уменьшал количество переделок: полдня написания плюс полдня переделки — дорого, и тест, ловящий ошибку рано, экономил этот день. Теперь и написание, и переделка стоят по пять минут. Довод «тест экономит переделку» правда умер.
Но проверка не подешевела ни на минуту. Запустить, посмотреть на числа, проверить края, убедиться, что соседнее не сломалось, — это те же полчаса, что и десять лет назад. И вот тут тест и окупается: он превращает эти полчаса в три секунды, и превращает каждый раз, а не один.
Есть и второй довод, который стал важнее именно теперь. Правок стало в три раза больше — это прямое следствие ускорения написания. Каждая из них может задеть соседнее, и число «задеваний» растёт вместе с числом правок.
Раньше пять правок в неделю проверялись руками без особого труда. Пятнадцать правок в неделю руками уже не проверяются: на это уходит больше времени, чем на саму работу. Тест здесь не улучшение, а условие, при котором такая скорость вообще возможна.
Третий довод касается работы с агентом напрямую. Правку, написанную агентом, вы читаете — но читаете быстрее, чем если бы писали сами, и это неизбежно. Тест здесь выполняет роль второго читателя, причём неутомимого: он не пропустит из-за того, что правка выглядит разумно.
И честная оговорка, без которой разговор получается пропагандой. Тесты бывают вредны — когда их пишут ради количества, когда они проверяют реализацию вместо поведения, когда их сто и они падают все разом от одной невинной правки. Такой набор не помогает, а берёт налог с каждой правки, и его справедливо ненавидят.
Разница между полезным и вредным набором проходит не по числу тестов, а по выбору того, что проверяется. Об этом вторая глава — и это самая содержательная часть темы.
Что проверять: три круга
Главный вопрос темы не «как писать тесты», а что именно проверять. Покрыть всё нельзя: набор вырастет больше кода и станет тем самым налогом.
Отбор идёт по трём кругам, и порядок в них по убыванию цены ошибки.
Круг 1: где ошибка стоит денег или доверия. Расчёты, суммы, скидки, налоги, права доступа, необратимые действия, обмен с чужими системами. Здесь тесты нужны всегда, и здесь же они окупаются быстрее всего: ручная проверка расчёта — это подготовить данные, посчитать в уме и сравнить, то есть десять минут каждый раз.
Круг 2: то, что уже ломалось. Каждая починка оставляет после себя тест — это правило из прошлого урока, и оно же работает как способ отбора. Место, которое сломалось однажды, ломается снова: у него есть свойство, из-за которого это произошло, и свойство никуда не делось.
Круг 3: то, что часто меняют. Если файл правят каждую неделю, тест на него окупается за месяц. Если файл не трогали два года, тест на него — расход без дохода: он будет зелёным всегда и не поймает ничего.
Отдельно про то, что проверять не надо. Оформление, названия, порядок полей, вёрстку, простые передачи значения из одного места в другое. Такие тесты падают от любой безобидной правки, не найдя при этом ни одной ошибки, — и именно они создают репутацию тестам.
Есть надёжный признак вредного теста: он падает, когда поведение не изменилось. Переименовали функцию — упал; поменяли порядок вызовов, не меняя результата, — упал; вынесли кусок в отдельное место — упал. Такой тест проверяет реализацию, а не поведение, и мешает ровно тому, ради чего тесты и заводят: свободе менять внутренности.
И практический приём отбора, который экономит больше всего размышлений: напишите тест на то, что вы и так проверяете руками каждый раз. Если после правки расчёта вы открываете отчёт и смотрите на итоговую сумму — вот и тест. Он повторяет ваше движение, и потому точно проверяет то, что важно: важность уже подтверждена тем, что вы это делаете.
Три вида проверок и их цена
Проверки бывают трёх видов, и разница между ними — в том, сколько системы они поднимают.
| Вид | Что проверяет | Цена прогона | Сколько брать |
|---|---|---|---|
| отдельная | одну функцию с известными входами | миллисекунды | сотни |
| стыковая | кусок вместе с базой и соседями | секунды | десятки |
| сквозная | весь путь, как у человека | минуты | единицы |
Отдельная проверка берёт функцию, даёт ей вход, сравнивает выход. Она быстрая, стабильная и точная: когда она падает, место известно сразу. Её слабость — она ничего не говорит о том, вызывается ли эта функция вообще и с теми ли данными.
Стыковая поднимает кусок системы: функцию вместе с базой, обработчик вместе с разбором запроса. Она отвечает на вопрос, которого не видит отдельная: сходятся ли части друг с другом. Большинство настоящих ошибок живёт именно на стыках, поэтому этот вид часто оказывается самым полезным на единицу усилий.
Сквозная проходит весь путь так, как его проходит человек. Она ближе всего к правде и хуже всего себя ведёт: медленная, зависит от всего сразу, падает от вещей, не имеющих отношения к делу. Держать их стоит единицы — на главные пути, те, поломка которых означает «система не работает».
Практическое правило выбора вида такое: берите самый дешёвый вид, который может поймать вашу ошибку. Ошибка в формуле ловится отдельной проверкой. Ошибка «забыли передать параметр в запрос» — только стыковой. Ошибка «кнопка не нажимается» — только сквозной.
Отдельно про скорость, потому что она решает больше, чем кажется. Набор, который идёт дольше двух минут, перестают запускать. Это не про дисциплину, а про то, как устроено внимание: две минуты ждут, десять минут — уходят делать другое и теряют нить.
И про надёжность. Тест, падающий через раз, надо чинить или удалять, третьего не дано. Такой тест обучает пропускать красный цвет, и обучает быстро: после третьего случайного падения человек перестаёт читать, что именно упало. Один ненадёжный тест обесценивает весь набор.
Причины случайных падений почти всегда в одном из трёх: тест зависит от времени, от порядка запуска или от данных, оставшихся от предыдущего прогона. Все три чинятся, и чинить их стоит сразу — они не рассасываются.
Тесты и агент: главная ловушка
Теперь про агента, и здесь есть одна ловушка, которая стоит всей главы.
Хорошая новость: писать тесты агент умеет отлично. Это работа с понятным входом и выходом, с большим количеством образцов, механическая по сути. Он напишет двадцать проверок на функцию за минуту, и они будут разумно оформлены.
Плохая новость — в том, откуда он берёт ожидаемый результат.
Такой тест бесполезен ровно в том случае, ради которого тесты и пишут: когда в коде есть ошибка. Он её закрепит и будет защищать. Более того, он сделает поведение труднее исправимым: попытка починить функцию уронит тест, и следующий человек решит, что чинить не надо.
Отсюда правило, простое и жёсткое: ожидаемый результат называет человек, а не код.
| Что делает человек | Что делает агент |
|---|---|
| решает, что проверять | пишет проверки по названному списку |
| называет ожидаемый результат | оформляет его как тест |
| перечисляет края, которые важны | пишет проверки на каждый |
| читает упавший тест и решает, кто прав | прогоняет и показывает разницу |
Практически это выглядит так: вы говорите «на пустом списке должна быть ошибка, а не ноль; на одном элементе — он сам; на отрицательных — тоже ошибка», а он превращает это в код проверок. Числа и ожидания — ваши, оформление — его.
Есть и вторая ловушка, более тонкая. Агент склонен писать много мелких проверок на то, что легко проверить, — потому что это самая простая часть задачи. Получается двадцать тестов на разбор строки и ни одного на главный расчёт, который сложнее сформулировать.
Лечится это тем же способом, что и первая: список того, что проверять, приносите вы. Три круга из второй главы — и есть этот список.
Наконец, про починку упавших тестов, потому что здесь прячется самая дорогая ошибка. Когда тест падает, есть два объяснения: сломался код или устарел тест. Выбор между ними — решение, и принимать его должен человек.
Поручение «тесты падают, почини» почти всегда приводит к тому, что подгоняются тесты: это короче, чем разбираться в коде, и результат выглядит одинаково — всё зелёное. Правильное поручение другое: «тест упал; покажи, что изменилось в поведении». Дальше вы решаете, было ли это изменение намеренным.