TDD и покрытие тестами
Написал один тест на счастливый путь и посмотрел покрытие. По строкам - 64%. Включил учёт ветвлений - 62%, и отчёт показал, что одна ветка условия не проверена ни разу. Разница в два процента выглядит мелочью, а за ней стоит целая непроверенная ветка обработки неверного ввода.
Стержень: покрытие показывает, какой код ВЫПОЛНЯЛСЯ, а не какой проверен; относиться к нему надо как к поиску дыр, а не как к оценке качества.
// Формулировки: «какое покрытие считать достаточным?», «чем branch coverage отличается от line?», «что такое TDD (test-driven development) и зачем?»
Что покрытие измеряет на самом деле
Инструмент считает, какие строки выполнились во время прогона. Он ничего не знает о том, проверил ли тест результат: строка, которую вызвали и не сверили ни с чем, засчитывается как покрытая. Именно поэтому набор с высоким покрытием и слабыми проверками ловит меньше, чем кажется.
Учёт ветвлений строже: он считает не строки, а переходы. Условие, которое всегда выполнялось только в одну сторону, считается покрытым по строкам и НЕ покрытым по ветвям. В моём замере это дало 64% против 62% и прямое указание на непроверенную ветку.
// Отсюда практика: включать учёт ветвлений всегда, а на само число смотреть не как на оценку, а как на карту. Полезно не «поднять с 64 до 80», а открыть отчёт и посмотреть, ЧТО именно не покрыто. Обычно там обнаруживается обработка ошибок - самая опасная часть кода, потому что выполняется она редко и именно в плохой момент.
- покрытие строк
- какие строки выполнились; о проверке результата ничего не говорит
- покрытие ветвлений
- какие переходы условий выполнились; строже и полезнее
Какой процент нужен
Универсального числа нет, а требование ста процентов вредно: последние проценты набираются тестами на невоспроизводимые ветки и на автосгенерированный код, и стоят они дороже, чем приносят.
Работающий подход другой. Ставится порог, который сейчас достигнут, и запрещается его СНИЖАТЬ. Отдельно требуется покрытие для НОВОГО кода - обычно заметно выше общего. Так набор растёт естественно, а старый код чинится по мере того, как его трогают.
// И ещё одна практика, которая ценнее процентов: смотреть покрытие критичных модулей отдельно. Расчёт денег, права доступа, обработка платежей должны быть покрыты плотно, а вспомогательные скрипты - как получится. Средняя температура по больнице тут врёт особенно сильно.
- порог без снижения
- текущее покрытие фиксируется, падать ниже нельзя
- покрытие нового кода
- требование к изменению, а не ко всему проекту сразу
Разработка через тесты
Цикл простой: сначала пишется падающий тест, потом минимальный код, чтобы он прошёл, потом код приводится в порядок при зелёных тестах. Смысл не в самих тестах, а в двух побочных эффектах.
Первый: тест пишется ДО кода, значит, вы вынуждены сначала придумать интерфейс - как эту функцию будут вызывать. Код, спроектированный от вызова, обычно удобнее того, что спроектирован изнутри. Второй: тест гарантированно проверен на способность краснеть, потому что вы видели его красным. Тест, который никогда не падал, может быть сломан и вы об этом не знаете.
// Честно про границы: подход не универсален. Он плохо ложится на исследовательскую работу, где неизвестно, что должно получиться, и на задачи, где интерфейс диктуется снаружи. На собесе разумно так и сказать: применяю там, где поведение известно заранее, а не как религию.
- цикл разработки через тесты
- красный тест, минимальный код, приведение в порядок
- проверенная способность краснеть
- тест видели упавшим, значит, он действительно что-то проверяет
Как отвечать: «Какое покрытие считать достаточным?»
Я не считаю покрытие оценкой качества - это карта того, какой код выполнялся. Строка, которую вызвали и не сверили ни с чем, засчитывается покрытой, поэтому набор с высоким процентом и слабыми проверками ловит меньше, чем кажется. Поэтому первое, что делаю, - включаю учёт ветвлений вместо строк. Я мерил на маленьком примере: один тест на счастливый путь дал 64% по строкам и 62% по ветвям, и отчёт прямо показал непроверенную ветку обработки неверного ввода. Дальше вместо погони за числом смотрю, ЧТО не покрыто: обычно это обработка ошибок, самая опасная часть кода. А в конвейере ставлю не абсолютный порог, а два правила: не снижать текущее покрытие и требовать высокое покрытие для нового кода. Отдельно смотрю критичные модули - расчёт денег и права доступа - там нужна плотность, а не средняя температура по проекту.
Ответ отказывается от числа как цели, объясняет разницу двух видов покрытия на замере и даёт работающее правило для конвейера. Это разговор человека, который жил с таким требованием.
На чём валятся
- −Считают высокое покрытие доказательством качества тестов.
- −Меряют покрытие строк вместо ветвлений и не видят непроверенных условий.
- −Гонятся за ста процентами и пишут бессмысленные тесты ради последних процентов.
- −Смотрят среднее по проекту и не видят дыры в критичных модулях.
- −Никогда не видели свой тест красным и не знают, работает ли он.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 14, остальные разбираются в тренажёре.
- Что именно меряет покрытие кода (coverage)?A)Какие строки и ветки кода выполнились во время тестовB)Сколько багов осталось в коде после прогона всего набора тестовC)Долю требований к продукту, закрытых написанными тест-кейсамиD)Насколько корректны сами ассерты в тестах по отношению к спеке
показать ответ и разбор
+A)Какие строки и ветки кода выполнились во время тестов// разбор: Coverage считает, какие строки (line coverage) и ветвления (branch coverage) исполнились хотя бы раз за прогон тестов. Это метрика исполнения, а не проверенности: строка «покрыта», даже если тест её выполнил, но ничего про результат не проверил. Полезно как индикатор дыр, вредно как самоцель.
- Модуль показывает 100% покрытие. Значит ли это, что в нём нет багов?A)Нет, потому что 100% покрытия в Python недостижимо в принципеB)Да, но только если все тесты в модуле зелёные на момент замераC)Нет: строка исполнена не значит, что её результат проверенD)Да, 100% покрытия математически подтверждает отсутствие ошибок
показать ответ и разбор
+C)Нет: строка исполнена не значит, что её результат проверен// разбор: 100% покрытия означает лишь, что каждая строка/ветка исполнилась хотя бы раз, но не что результат где-то проверен ассертом и что покрыты все комбинации входов и эдж-кейсы. Тест без единого assert поднимает покрытие, ничего не проверяя. Покрытие ловит непротестированные куски, но не доказывает корректность.
- С чего в TDD начинают работу над новой функцией?A)С рефакторинга соседнего кода, чтобы освободить место под новоеB)С настройки CI и покрытия, чтобы метрики были зелёными заранееC)С полной реализации функции, тесты пишут уже под готовый кодD)С падающего теста, описывающего желаемое поведение
показать ответ и разбор
+D)С падающего теста, описывающего желаемое поведение// разбор: В TDD первым пишут тест на ещё не существующее поведение — он падает (Red), фиксируя контракт: что функция должна делать. Затем добавляют минимальный код до зелёного и рефакторят. Начинать с теста заставляет продумать интерфейс и наблюдаемый результат раньше деталей реализации.
- Что описывает цикл TDD «red-green-refactor»?A)Сначала падающий тест (red), затем минимальный код до зелёного, потом рефакторB)Три независимые команды разработчиков, каждая из которых отвечает за свой цвет этапаC)Сначала весь код целиком, потом к нему тесты, и в конце общий рефакторинг проектаD)Красным помечают плохой код, зелёным — хороший, а рефактор убирает всё красное разом
показать ответ и разбор
+A)Сначала падающий тест (red), затем минимальный код до зелёного, потом рефактор// разбор: TDD идёт короткими циклами: red — пишем падающий тест на желаемое поведение (его ещё нет); green — минимальный код, чтобы тест прошёл; refactor — улучшаем структуру, держа тесты зелёными. Тест ведёт дизайн и служит спецификацией, а рефактор безопасен под защитой тестов. Ключевое отличие от test-after — тест появляется ДО реализации.
- Покрытие тестами (coverage) — 100%. Значит ли это, что багов нет?A)Да: сто процентов покрытия — это гарантия отсутствия ошибок в протестированном кодеB)Нет: покрытие показывает исполненные строки, а не корректность и все сценарииC)Нет, потому что 100% покрытия в реальном проекте технически недостижимо в принципеD)Да, но лишь при условии, что все тесты в наборе на момент замера были зелёными
показать ответ и разбор
+B)Нет: покрытие показывает исполненные строки, а не корректность и все сценарии// разбор: Coverage измеряет, какие строки/ветви исполнились во время тестов, а не проверяет корректность. Можно прогнать строку без единого осмысленного assert, «покрыв» её, но не поймав ошибку. 100% покрытия не гарантирует, что проверены все входные данные, граничные случаи и комбинации. Покрытие — полезный ИНДИКАТОР непротестированных мест, но не цель и не доказательство правильности.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.