Объяснение покрытия кода: покрытие линий или ветвей и что на самом деле означает 100%

  1. Главная
  2. Блог
  3. Тестирование программного обеспечения

Тестирование программного обеспечения

Объяснение покрытия кода: Покрытие линий и ветвей и что на самом деле означает 100%

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

8 мин чтения
Объяснение покрытия кода: покрытие линий и ветвей и что на самом деле означает 100%

Главное

  • Покрытие кода измеряет, какая часть вашего кода выполняется во время тестов.
  • Покрытие филиалов более строгое, чем покрытие линии, и более полезно.
  • В нашем тесте пакет с без утверждений все же достиг 100% покрытия.
  • Используйте покрытие для поиска непроверенного кода, а не в качестве доказательства качества.

Команда с гордостью сообщает о 95% покрытии кода, а затем отправляет ошибку в «покрытой» строке. Это не противоречие. Покрытие отвечает на один узкий вопрос: выполняли ли тесты этот код? Проверили ли они результат – это совсем другой вопрос.

Мы использовали Python с Cover.py 7.17 и pytest 9.2. Все цифры и отчеты ниже взяты из нашего собственного опыта. Идеи применимы на любом языке.

Что такое покрытие кода?

Покрытие кода — это процент вашего кода, который выполняется во время выполнения автоматических тестов. Инструмент записывает, какие строки и, при необходимости, какие ветви были выполнены, а затем сообщает, что было пропущено.

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

Виды покрытия

Тип Вопрос, на который он отвечает
Функция Была ли каждая функция вызвана хотя бы один раз?
Строка (заявление) Каждая строка запускалась хотя бы один раз?
Филиал Каждый из if пошел в обе стороны: истинный и ложный?
Состояние Принимала ли каждая часть комбинированного условия (a and b) оба значения?
MC/DC Изменило ли каждое условие независимо результат? Используется в критически важном для безопасности программном обеспечении.

Каждый уровень более строгий, чем предыдущий. Большинство команд отслеживают покрытие линий и филиалов. Покрытие условий и MC/DC появляются в авиационных и автомобильных стандартах, где специализирована инструментальная поддержка.

Код, который мы измерили

def shipping_fee(total, is_member, express=False):
    if total < 0:
        raise ValueError("total cannot be negative")
    fee = 0 if total >= 1000 else 50
    if is_member:
        fee = max(fee - 25, 0)
    if express:
        fee += 100
    return fee

Наш первый тестовый файл содержал два теста:

def test_regular_order():
    assert shipping_fee(500, False) == 50

def test_member_express():
    assert shipping_fee(500, True, express=True) == 125

Мы запустили его с включенным измерением ветвей:

coverage run --branch -m pytest test_shipping.py
coverage report -m

Чтение отчета

Name          Stmts   Miss Branch BrPart  Cover   Missing
---------------------------------------------------------
shipping.py       9      1      6      1    87%   3
  • Stmts 9, Miss 1: девять исполняемых операторов, один из которых никогда не выполнялся.
  • Ветвь 6, BrЧасть 1: три if решения дают шесть возможных результатов. Одно решение всегда было только в одну сторону.
  • Отсутствует 3: строка 3, raise ValueError, никогда не выполнялась. Ни один тест не дал отрицательного результата.

Исправление представляет собой проверку пути ошибки. Также мы добавили тест на крупный заказ:

def test_free_over_1000():
    assert shipping_fee(1000, False) == 0

def test_negative_total():
    with pytest.raises(ValueError):
        shipping_fee(-1, False)

Для обоих файлов отчет показал 100% отсутствие пропущенных строк или частичных ветвей. Пути ошибок — это классический метод поиска покрытия пробелов, поэтому тесты на недопустимый ввод, разработанные с использованием таких методов, как эквивалентное разделение, так хорошо сочетаются с ним.

Что скрыл отчет

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

Причина в том, что fee = 0 if total >= 1000 else 50 находится на одной строке. Cover.py измеряет ответвления между строками, поэтому условное выражение внутри одной строки считается одним выполненным оператором. Если бы в этом правиле была ошибка, например > вместо >=, первый набор все равно показал бы 87% без подсказки. Проверка самой границы, как описано в анализе граничных значений, — вот что ее улавливает.

100% покрытие без проверок

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

def test_runs_everything_but_checks_nothing():
    shipping_fee(500, True, express=True)
    shipping_fee(1000, False)
    shipping_fee(500, False)
    try:
        shipping_fee(-1, False)
    except ValueError:
        pass
shipping.py       9      0      6      0   100%

Достигнуто 100% покрытие линий и филиалов. Функция может возвращать любое число, и этот тест все равно пройдет. Это основной предел охвата: он измеряет исполнение, а не проверку.

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

Чтобы определить, действительно ли тесты выявляют ошибки, используйте мутационное тестирование. Такой инструмент, как mutmut для Python, PIT для Java или Stryker для JavaScript, вносит небольшие изменения в ваш код, например меняет >= на >, и проверяет, не пройден ли тест. Наш тест без подтверждений позволил бы выжить каждой мутации.

Какую цель поставить

Не существует универсально правильного числа. Практическое руководство:

  • Не гонитесь за 100%. Последние несколько процентов часто означают тестирование тривиального кода или написание тестов, которые просто касаются строк.
  • Многие команды стремятся к общему показателю примерно от 70 до 85% с более высокими ожиданиями в отношении основной бизнес-логики.
  • Следите за тенденцией, а не за цифрами. Снижение охвата каждого выпуска — предупреждающий знак.
  • Требуйте включения нового кода в запросах на включение, а не заставляйте сразу весь старый код.
  • Исключите то, что не имеет значения, например сгенерированный код, с помощью четких правил.

Действует закон Гудхарта: когда показатель становится целью, он перестает быть хорошим показателем. Жесткие цели поощряют такие тесты, как наш пример без утверждений.

Инструменты по языку

Язык Общие инструменты
Питон покрытие.py, pytest-cov
JavaScript/ТипСкрипт Стамбул (Нью-Йорк), встроенный в Jest и Vitest
Ява ДжаКоКо
.NET Покрывало
Иди пойти проверить - обложка
Отчетность Codecov, SonarQube, Комбинезоны

Покрытие в CI

Запустите покрытие как часть вашего конвейера CI и опубликуйте отчет, чтобы рецензенты могли видеть, какие новые линии не протестированы. В случае покрытия.py coverage report --fail-under=80 сборка ниже порогового значения не удалась. Сохраняйте любые пороговые значения умеренными и комбинируйте их с правилом для нового кода, чтобы один большой устаревший файл не блокировал все изменения.

Помимо освещения: лучшие вопросы, которые стоит задать

  • Охватывают ли тесты рискованные, часто изменяемые части кода?
  • Проверяются ли границы и пути ошибок, а не только счастливый путь?
  • Будет ли тест провален, если эта строка окажется неверной?
  • Достаточно ли быстры и стабильны тесты, чтобы их можно было запускать при каждом изменении?

Они принадлежат любой стратегии тестирования. Освещение помогает ответить только на первое.

Исключение кода и чтение отчетов HTML

Некоторый код не стоит тестировать напрямую, например, помощники отладки или блок, который запускается только при ручном запуске сценария. Coverage.py позволяет исключить строки с комментарием:

if __name__ == "__main__":  # pragma: no cover
    main()

Вы также можете установить исключения и исходные папки для измерения в конфигурации, например в pyproject.toml:

[tool.coverage.run]
branch = true
source = ["shop"]

[tool.coverage.report]
show_missing = true
exclude_also = ["if TYPE_CHECKING:"]

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

Для повседневной работы отчет HTML более полезен, чем таблица терминала. Запустите coverage html и откройте htmlcov/index.html. Каждый файл показан с выполненными строками зеленым цветом, пропущенными строками красным и частичными ветвями желтым, так что вы можете сразу увидеть, какой if никогда не шел по другому пути. Многие редакторы также могут отображать покрытие на полях, что делает пробелы видимыми во время написания кода, а не после него.

Покрытие до рефакторинга

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

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

Итоги

Покрытие кода — это фонарик, а не оценка. Используйте покрытие ветвей, чтобы найти код, который не затрагивается ни одним тестом, особенно обработку ошибок, а затем пишите тесты, которые делают реальные утверждения. Если вы помните один результат нашего эксперимента, назовите его таким: 100%-ное покрытие было достигнуто с помощью теста, который ничего не проверял.

Часто задаваемые вопросы

Что такое покрытие кода при тестировании?

Это процент кода, выполняемого во время выполнения автоматических тестов, измеряемый строками, ветвями или условиями. Он показывает, чего коснулись тесты, а не подтвердили ли они это.

В чем разница между покрытием линии и филиала?

Покрытие линии проверяет, чтобы каждая линия работала. Охват филиалов проверяет, чтобы каждое решение принималось в обе стороны. Покрытие филиалов находит непроверенные пути, которые пропускает покрытие линий.

100% покрытие кода — это хорошо?

Не сам по себе. В нашем тесте набор без утверждений достиг 100%. Стремитесь к содержательным тестам важного кода, а не к идеальному числу.

Каков хороший процент покрытия кода?

Многие команды в целом нацелены примерно на 70–85%, уделяя больше внимания критической логике. Тенденция и качество утверждений имеют большее значение, чем точное количество.

Чем мутационное тестирование отличается от страхового покрытия?

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

Продолжайте учиться

Работайте с Stack Quora

Нужна помощь с тестированием, WordPress или SEO?

Мы помогаем командам выпускать лучшее программное обеспечение и развивать свои сайты. Расскажите нам, над чем вы работаете, и мы предложим четкий следующий шаг.

Продолжайте читать

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

Понравилась эта статья? Выберите тему и сразу приступайте к нашим новейшим руководствам.

Искусственный интеллект

Объяснение галлюцинаций искусственного интеллекта: почему языковые модели все выдумывают и как их уменьшить

Галлюцинации ИИ объяснили: что это такое, почему языковые модели их создают, распространенные типы, как уменьшить их с помощью заземления и подсказо…

Читать статью →
DevOps

Объяснение переменных среды: файлы .env, секреты и правильно выполненная конфигурация

Переменные среды объясняются на примерах Python, которые мы запускали: их чтение и установка, почему каждое значение является строкой, файлы .env, н…

Читать статью →
Разработка ПО

Объяснение ограничения скорости API: алгоритмы, заголовки и обработка ошибок 429

Ограничение скорости API объясняется с помощью проведенного нами моделирования: сегмент токенов, фиксированное окно, скользящее окно, 429 ответов, з…

Читать статью →
Автоматизация тестирования

Издевательство в Python: unittest.mock, патч и ошибки, из-за которых тесты лгут

Издевательство на Python с помощью unittest.mock, протестировано: Mock и return_value, Side_effect, исправление использования имени, спецификация и …

Читать статью →
Тестирование ПО

Объяснение покрытия кода: покрытие линий или ветвей и что на самом деле означает 100%

Покрытие кода объяснено с помощью реального запуска покрытия.py: покрытие строк, ветвей и условий, чтение отчета, почему 100%-ное покрытие все еще м…

Читать статью →
Базы данных и SQL

DELETE, TRUNCATE и DROP в SQL: различия, откат и когда использовать каждый из них

Объяснение DELETE, TRUNCATE и DROP на примере протестированного SQL: что каждый из них удаляет, откат, триггеры, сброс идентификаторов, скорость и ч…

Читать статью →
Базы данных и SQL

Объяснение представлений SQL: создавайте, используйте и избегайте ловушек, с примерами

Представления SQL, объясненные с помощью запросов, которые мы выполняли: создание представления, результаты в реальном времени, скрытие столбцов, пр…

Читать статью →
Разработка ПО

Интерфейс TypeScript и тип: реальные различия, протестированные с помощью компилятора

Интерфейс TypeScript против типа, проверенный реальным компилятором: что могут делать оба, что может делать только один, слияние объявлений, расшире…

Читать статью →
Разработка ПО

Объяснение Python *args и **kwargs: гибкие функции с понятными примерами

Python *args и **kwargs, объясненные с помощью кода, который мы запускали: упаковка и распаковка, порядок параметров, аргументы только по ключевым с…

Читать статью →
Разработка ПО

Лямбда-функции Python: синтаксис, реальное использование и когда их не следует использовать

Лямбда-функции Python, объясненные с помощью кода, который мы запускали: синтаксис, сортировка с помощью ключа, карты и фильтра, замыкания, ловушка …

Читать статью →
Искусственный интеллект

Что такое ИИ-агенты? Как они работают: можно запустить простой цикл агента

Что такое агенты ИИ? Простое руководство по циклу агента, инструментам, памяти и ограничениям, с небольшим циклом Python, который мы запустили, реал…

Читать статью →
DevOps

Мониторинг и наблюдаемость: реальная разница с журналами, метриками и трассировками

Объяснение мониторинга и наблюдаемости: известные и неизвестные сбои, журналы, метрики и трассировки, пошаговое руководство по инцидентам при оформл…

Читать статью →
DevOps

Docker Volumes против Bind Mounts: правильное сохранение данных контейнера

Объяснение томов Docker и монтирования привязки: почему исчезают данные контейнера, именованные тома, монтирование привязки, tmpfs, примеры создания…

Читать статью →
Автоматизация тестирования

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

TDD и BDD объясняются с помощью одной функции скидки, созданной в обоих направлениях: красно-зеленый рефакторинг с кодом, который мы запускали, сцен…

Читать статью →
Разработка ПО

Git merge vs rebase: отличия, примеры и когда что использовать

Git merge vs rebase с реальным выводом log, сравнительной таблицей, золотым правилом rebase и безопасным рабочим процессом для команды.

Читать статью →
Разработка ПО

Генераторы списков в Python: синтаксис, примеры и когда их избегать

Генераторы списков в Python: синтаксис, фильтры, if-else, вложенные циклы, измеренное время и случаи, когда обычный цикл for лучше.

Читать статью →
Базы данных и SQL

SQL JOIN: объяснение с примерами (Inner, Left, Right, Full)

Разберите SQL JOIN шаг за шагом на примерах таблиц и запросов: INNER, LEFT, RIGHT, FULL, CROSS и SELF, а также частые ошибки и советы по производите…

Читать статью →