сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · Тесты в Python

TDD и покрытие тестами

Покрытие и разработка через тесты

Написал один тест на счастливый путь и посмотрел покрытие. По строкам - 64%. Включил учёт ветвлений - 62%, и отчёт показал, что одна ветка условия не проверена ни разу. Разница в два процента выглядит мелочью, а за ней стоит целая непроверенная ветка обработки неверного ввода.

Стержень: покрытие показывает, какой код ВЫПОЛНЯЛСЯ, а не какой проверен; относиться к нему надо как к поиску дыр, а не как к оценке качества.

// Формулировки: «какое покрытие считать достаточным?», «чем branch coverage отличается от line?», «что такое TDD (test-driven development) и зачем?»

Что покрытие измеряет на самом деле

Инструмент считает, какие строки выполнились во время прогона. Он ничего не знает о том, проверил ли тест результат: строка, которую вызвали и не сверили ни с чем, засчитывается как покрытая. Именно поэтому набор с высоким покрытием и слабыми проверками ловит меньше, чем кажется.

Учёт ветвлений строже: он считает не строки, а переходы. Условие, которое всегда выполнялось только в одну сторону, считается покрытым по строкам и НЕ покрытым по ветвям. В моём замере это дало 64% против 62% и прямое указание на непроверенную ветку.

// Отсюда практика: включать учёт ветвлений всегда, а на само число смотреть не как на оценку, а как на карту. Полезно не «поднять с 64 до 80», а открыть отчёт и посмотреть, ЧТО именно не покрыто. Обычно там обнаруживается обработка ошибок - самая опасная часть кода, потому что выполняется она редко и именно в плохой момент.

покрытие строк
какие строки выполнились; о проверке результата ничего не говорит
покрытие ветвлений
какие переходы условий выполнились; строже и полезнее

Какой процент нужен

Универсального числа нет, а требование ста процентов вредно: последние проценты набираются тестами на невоспроизводимые ветки и на автосгенерированный код, и стоят они дороже, чем приносят.

Работающий подход другой. Ставится порог, который сейчас достигнут, и запрещается его СНИЖАТЬ. Отдельно требуется покрытие для НОВОГО кода - обычно заметно выше общего. Так набор растёт естественно, а старый код чинится по мере того, как его трогают.

// И ещё одна практика, которая ценнее процентов: смотреть покрытие критичных модулей отдельно. Расчёт денег, права доступа, обработка платежей должны быть покрыты плотно, а вспомогательные скрипты - как получится. Средняя температура по больнице тут врёт особенно сильно.

порог без снижения
текущее покрытие фиксируется, падать ниже нельзя
покрытие нового кода
требование к изменению, а не ко всему проекту сразу

Разработка через тесты

Цикл простой: сначала пишется падающий тест, потом минимальный код, чтобы он прошёл, потом код приводится в порядок при зелёных тестах. Смысл не в самих тестах, а в двух побочных эффектах.

Первый: тест пишется ДО кода, значит, вы вынуждены сначала придумать интерфейс - как эту функцию будут вызывать. Код, спроектированный от вызова, обычно удобнее того, что спроектирован изнутри. Второй: тест гарантированно проверен на способность краснеть, потому что вы видели его красным. Тест, который никогда не падал, может быть сломан и вы об этом не знаете.

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

цикл разработки через тесты
красный тест, минимальный код, приведение в порядок
проверенная способность краснеть
тест видели упавшим, значит, он действительно что-то проверяет

Как отвечать: «Какое покрытие считать достаточным?»

Я не считаю покрытие оценкой качества - это карта того, какой код выполнялся. Строка, которую вызвали и не сверили ни с чем, засчитывается покрытой, поэтому набор с высоким процентом и слабыми проверками ловит меньше, чем кажется. Поэтому первое, что делаю, - включаю учёт ветвлений вместо строк. Я мерил на маленьком примере: один тест на счастливый путь дал 64% по строкам и 62% по ветвям, и отчёт прямо показал непроверенную ветку обработки неверного ввода. Дальше вместо погони за числом смотрю, ЧТО не покрыто: обычно это обработка ошибок, самая опасная часть кода. А в конвейере ставлю не абсолютный порог, а два правила: не снижать текущее покрытие и требовать высокое покрытие для нового кода. Отдельно смотрю критичные модули - расчёт денег и права доступа - там нужна плотность, а не средняя температура по проекту.

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

На чём валятся

  • Считают высокое покрытие доказательством качества тестов.
  • Меряют покрытие строк вместо ветвлений и не видят непроверенных условий.
  • Гонятся за ста процентами и пишут бессмысленные тесты ради последних процентов.
  • Смотрят среднее по проекту и не видят дыры в критичных модулях.
  • Никогда не видели свой тест красным и не знают, работает ли он.

Проверьте себя

Пять вопросов из банка по этой подтеме. Всего их 14, остальные разбираются в тренажёре.

  1. #tdd_coverage1 / 5
    Что именно меряет покрытие кода (coverage)?
    A)Какие строки и ветки кода выполнились во время тестов
    B)Сколько багов осталось в коде после прогона всего набора тестов
    C)Долю требований к продукту, закрытых написанными тест-кейсами
    D)Насколько корректны сами ассерты в тестах по отношению к спеке
    показать ответ и разбор
    +A)Какие строки и ветки кода выполнились во время тестов

    // разбор: Coverage считает, какие строки (line coverage) и ветвления (branch coverage) исполнились хотя бы раз за прогон тестов. Это метрика исполнения, а не проверенности: строка «покрыта», даже если тест её выполнил, но ничего про результат не проверил. Полезно как индикатор дыр, вредно как самоцель.

  2. #tdd_coverage2 / 5
    Модуль показывает 100% покрытие. Значит ли это, что в нём нет багов?
    A)Нет, потому что 100% покрытия в Python недостижимо в принципе
    B)Да, но только если все тесты в модуле зелёные на момент замера
    C)Нет: строка исполнена не значит, что её результат проверен
    D)Да, 100% покрытия математически подтверждает отсутствие ошибок
    показать ответ и разбор
    +C)Нет: строка исполнена не значит, что её результат проверен

    // разбор: 100% покрытия означает лишь, что каждая строка/ветка исполнилась хотя бы раз, но не что результат где-то проверен ассертом и что покрыты все комбинации входов и эдж-кейсы. Тест без единого assert поднимает покрытие, ничего не проверяя. Покрытие ловит непротестированные куски, но не доказывает корректность.

  3. #tdd_coverage3 / 5
    С чего в TDD начинают работу над новой функцией?
    A)С рефакторинга соседнего кода, чтобы освободить место под новое
    B)С настройки CI и покрытия, чтобы метрики были зелёными заранее
    C)С полной реализации функции, тесты пишут уже под готовый код
    D)С падающего теста, описывающего желаемое поведение
    показать ответ и разбор
    +D)С падающего теста, описывающего желаемое поведение

    // разбор: В TDD первым пишут тест на ещё не существующее поведение — он падает (Red), фиксируя контракт: что функция должна делать. Затем добавляют минимальный код до зелёного и рефакторят. Начинать с теста заставляет продумать интерфейс и наблюдаемый результат раньше деталей реализации.

  4. #tdd_coverage4 / 5
    Что описывает цикл TDD «red-green-refactor»?
    A)Сначала падающий тест (red), затем минимальный код до зелёного, потом рефактор
    B)Три независимые команды разработчиков, каждая из которых отвечает за свой цвет этапа
    C)Сначала весь код целиком, потом к нему тесты, и в конце общий рефакторинг проекта
    D)Красным помечают плохой код, зелёным — хороший, а рефактор убирает всё красное разом
    показать ответ и разбор
    +A)Сначала падающий тест (red), затем минимальный код до зелёного, потом рефактор

    // разбор: TDD идёт короткими циклами: red — пишем падающий тест на желаемое поведение (его ещё нет); green — минимальный код, чтобы тест прошёл; refactor — улучшаем структуру, держа тесты зелёными. Тест ведёт дизайн и служит спецификацией, а рефактор безопасен под защитой тестов. Ключевое отличие от test-after — тест появляется ДО реализации.

  5. #tdd_coverage5 / 5
    Покрытие тестами (coverage) — 100%. Значит ли это, что багов нет?
    A)Да: сто процентов покрытия — это гарантия отсутствия ошибок в протестированном коде
    B)Нет: покрытие показывает исполненные строки, а не корректность и все сценарии
    C)Нет, потому что 100% покрытия в реальном проекте технически недостижимо в принципе
    D)Да, но лишь при условии, что все тесты в наборе на момент замера были зелёными
    показать ответ и разбор
    +B)Нет: покрытие показывает исполненные строки, а не корректность и все сценарии

    // разбор: Coverage измеряет, какие строки/ветви исполнились во время тестов, а не проверяет корректность. Можно прогнать строку без единого осмысленного assert, «покрыв» её, но не поймав ошибку. 100% покрытия не гарантирует, что проверены все входные данные, граничные случаи и комбинации. Покрытие — полезный ИНДИКАТОР непротестированных мест, но не цель и не доказательство правильности.

дальше

Теорию прочитали. Навык ставится повторением

В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.