Как измерить покрытие автотестами? Данный вопрос всегда вызывает жаркие дискуссии в рядах тестировщиков. Некоторые меряют покрытие по требованиям, на сколько пунктов были написаны автотесты, такое покрытие и есть. Другие по задачам в бэклоге, где находятся нереализованные тесты и баги, которые требуют покрытия. В общем случае, измерять покрытие становится сложнее, когда мы движемся вверх по пирамиде тестирования и уровень наших тестов растет. Как здорово на уровне Unit тестов, там отлично показывается сколько строк кода покрыто, процент и т.д. Вот если бы подобный отчет можно было сделать для end-to-end тестов или UI, то сразу было бы понятно реальное тестовое покрытие и каких тестов не хватает. К счастью, есть такой инструмент, который позволяет строить подробный отчет по покрытию Python сервисов - Coverage.py.

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

Второй подход - это снятие покрытия кода в реальном времени. Утилита подключается к виртуальной машине или интерпретатору и регистрирует вывозы различных операций. После, сравнивает результат с исходным кодом и строит свой отчет. Такой подход используют утилиты, например, JaCoCo для Java и Coverage.py для Python. Отличия этих утилит друг от друга в том, что JaCoCo подключается к Java машине, а в основе Covarage.py лежит функция трассировки. Это функция, которую интерпретатор Python вызывает для каждой строки, выполняемой в программе. Covarage.py запускает проверяемый код в своем “врапере”, где выполняет функцию трассировки, которая записывает каждый файл и номер строки по мере его выполнения.

Посмотрим на примере, как работает Covarege.py. Есть простой API сервис. В котором 3 endpoint-а. Как видно, в 3-ем endpoint-е '/second/{branch}' идет параметризированный вывод. Параметр branch обязателен в этом запросе, поэтому код никогда не зайдет в ветку с сообщением 'Unreachable'

from fastapi import FastAPI

app = FastAPI()


@app.get('/')
async def index_page():
    return {'message': 'Hello habr'}


@app.get('/first')
async def first_page():
    return {'message': 'The first page'}


@app.get('/second/{branch}')
async def second_page(branch):
    msg_type = 'Reachable'
    if not branch:
        msg_type = 'Unreachable'
    return {'message': f'{msg_type} response'}

Полный код сервиса с примерами отчетов

Запускаем сервис во “врапере” Covarage.py. Дальше, открываем браузер и переходим по всем трем endpoint-ам.

coverage run -a manage.py start-test-app
Главная страница
Главная страница
Обращение к первой странице
Обращение к первой странице
Обращение ко второй странице
Обращение ко второй странице

После завершения “тестирования”, нужно остановить сервис, для того, чтобы Covarage.py сформировал файл .coverage, который является sqlite базой. Для представления отчета в удобном виде, сконвертируем его в html и откроем в браузере.

 coverage html
Отчет о тестовом покрытии по строчкам
Отчет о тестовом покрытии по строчкам

Отчет показывает 96% по всем файлами и 93% по файлу с кейсом, где одна ветка кода недостижима. Если мы “провалимся” в отчет по файлу main.py, то увидим, что данная строчка красная, но само условие зеленое.

Отчет о тестовом покрытии по строчкам файла main.py
Отчет о тестовом покрытии по строчкам файла main.py

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

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

coverage run --branch -a manage.py start-test-app

В итоге, Covarage.py сформировал отчет относительно ветвей исполнения кода. Как видно, покрытие упало до 90% по всем файлам и до 88% по файлу с кейсом. Появились две дополнительных колонки:

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

  • Partial - показывает количество ветвлений, где не сработало условие.

Отчет о тестовом покрытии по ветвлениям
Отчет о тестовом покрытии по ветвлениям

Заглянем в main.py. Видно, что строчка с ветвлением подсвечена желтым, а это значит, что это условие не выполнилось и как результат, данная ветка кода не отработала.

Отчет о тестовом покрытии по ветвлениям файла main.py
Отчет о тестовом покрытии по ветвлениям файла main.py

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

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

Комментарии (0)