Суть
Покрытие кода — это процент кода, покрытый тестами.
Другими словами, покрытие кода показывает, какая часть кода приложения была проверена при выполнении (автоматизированных) тестов.
В большинстве случаев, когда говорят о покрытии кода, имеют в виду юнит-тестирование (модульное, выполняемое разработчиками); когда говорят о тестовом покрытии, это обычно относится к тестированию «в общем» (ко всем другим видам и уровням тестирования, которое выполняется QA-инженерами/QA-департаментом). О тестовом покрытии — отдельный материал.
Почему важен показатель тестового покрытия
Цель разработки любого приложения — создать качественный продукт без багов, удовлетворить требования заказчика и пожелания пользователей.
Юнит-тестирование повышает уверенность разработчиков, что в их коде отсутствуют дефекты на фундаментальном уровне (уровне юнитов кода). Проджект-менеджеры стремятся повысить покрытие кода, комбинируя разные методы оценки этого покрытия. Тут важны следующие нюансы.
Во-первых, польза от юнит-тестов неизвестна, пока неизвестно их покрытие. Зная показатель покрытия, можно приблизительно знать, какая часть кода (уже) проверена.
Юнит-тестирование, скорее всего, будет не очень эффективным без покрытия как минимум основных сценариев, пользовательских путей, и негативных тест-кейсов. Метрики покрытия дают понимание, что в коде еще не проверено, где еще могут быть дефекты.
Второе: отслеживание метрик покрытия с самого начала проекта — полезная практика с той точки зрения, что ошибки систематически устраняются с самого начала цикла. То есть будет меньше технический долг.
Третье: скорость. Подсчет цифровых метрик покрытия кода и их отслеживание с самого начала — существенно ускоряет разработку, а потом и сопровождение кода. Скорость разработки и сопровождения важна собственникам компании: выше рентабельность, быстрее возврат инвестиций. Приложение, изначально качественное на фундаментальном уровне, скорее всего будет достаточно качественным и на других уровнях.
Четвертое: добросовестно проведенные юнит-тесты сокращают объем тестирования на высших уровнях «пирамиды». Разработчики экономят время тестировщиков.
Далее рассмотрим самые применяемые метрики покрытия кода.
Типы покрытия кода
Покрытие операторов (statement coverage)
Суть : каждый оператор должен быть выполнен хотя бы один раз.
В императивных языках программирования оператором называется самая малая часть программного кода, которая выполняет действие.
Показатель покрытия операторов:
(Количество выполненных операторов / Общее количество операторов) * 100
Покрытие операторов имеет такие полезные функции:
- Проверить логику кода
- Найти «мертвый код», незадействованные операторы
- Проверить пути выполнения и найти среди них непокрытые
Пример покрытия операторов
Программа на условном ЯП (псевдокод), суммирующая два числа и выводящая разные результаты, если результат меньше/равен/больше 0:
Unittest1: SUM(a,b)
result = a + b; if result > 0 then print(‘Больше нуля’); else if result < 0 then print(‘Меньше нуля’); else print(‘Равно 0’); end
Каким будет покрытие операторов здесь? В первом операторе ввод, например, a=3, b=5. Будут выполнены операторы в строках 1-4. Таким образом, покрытие операторов составит 4 оператора из 8 имеющихся, то есть 50%.
Примем a=3, b=-5. Тогда будут выполнены операторы в строках 1,2,3,5,6. В первом и втором тестах будут вызваны и выполнены операторы в строках с 1 по 6, то есть 6 из 8 строк, покрытие операторов ( = строк) составит 75%.
Покрытие ветвей (branch coverage)
Также называемое покрытием решений. В этом подходе, имеющем свои преимущества, каждая ветвь в так называемом дереве решений будет выполнена хотя бы по одному разу. «Ветвями» могут быть как условные операторы («ветвящие» выполнение кода), так и циклы, так и операторы переключения (типа switch). Покрытие ветвей (решений) вычисляется аналогично первому примеру:
(Количество выполненных ветвей / Общее количество ветвей)* 100
Что дает покрытие ветвей:
- Проверяет полноту покрытия решений
- А также проявляет возможное некорректное поведение каждой ветви
- И, что важнее, проверяет участки кода, труднодоступные для других методов оценки покрытия
Пример покрытия ветвлений
Ниже псевдокод программы, которая выводит введенные пользователем числа, а также выводит сообщение, если число четное:
Unittest2: EVEN(a)
if a is even then print(‘Четное’); print(a);
Здесь имеем условия, а значит и ветвления, которые нужно покрыть.
На рисунке два условных ветвления, “Да” и “Нет”. Стрелкой без подписи обозначено ветвление без условия (unconditional branch) — метод покрытия ветвей учитывает и эти ветви. Имеем два тестовых сценария:
- Например a = 1. Выполнение пойдет по ветви “Нет”. Тогда покрытие ветвей составит ⅓ строк, 33%.
- Примем a = 4; Выполнение пойдет по ветви “Да” и по ветке без условия, и покрытие составит ⅔ = 67%. Покрытие кода составит 33%+67%=100%.
Покрытие функций (function coverage)
Этот подход проверяет, вызывается ли каждая функция в коде хотя бы один раз. Также могут проверяться параметры функций, с которыми они вызываются. Таким образом, тестовый набор проверяет корректность поведения функций при разных сценариях.
Как и в предыдущих примерах, покрытие функций вычисляют по простой формуле:
(Количество выполненных функций / Общее количество функций)* 100
Например, если программа состоит только из одного метода, один юнит-тест этого метода приведет к 100% покрытию функций. Но очевидно, что один юнит-тест не может покрыть все возможные пути выполнения, сценарии и параметры. Несмотря на стопроцентное покрытие функций, приложение явно недостаточно протестировано.
Как видим выше, высокое, даже 100%-ное покрытие кода не всегда гарантирует, что в коде нет багов.
Идеальное покрытие
Когда говорят об «идеальном покрытии», имеют в виду 100% или около того — тогда код должен быть близок к совершенству. Все сложнее.
Во-первых, зависит от текущего состояния проекта и принятых методик. Если измерять покрытие кода с самого начала разработки, возможно получить покрытие выше 90%, это отлично. Такое часто бывает, если компания работает по TDD-методике разработки.
Почти невозможно достичь такого высокого покрытия в крупном длительном проекте с большим количеством legacy-кода, плохо покрытого тестами. В таких случаях тестируют только новые функции и пытаются последовательно покрывать существующие функции, при их модификации или расширении функциональности. В подобных проектах и 30% покрытия кода будет выглядеть неплохим результатом.
Во-вторых, достижение стопроцентного покрытия кода не может быть самодостаточной целью. Разработчики будут писать бесполезные юнит-тесты «для галочки», просто чтобы достичь целевого покрытия. Это приведет к пропуску или некорректной имплементации требований; разработчики будут распыляться, думать о покрытии, а не о требованиях и совершенствовании бизнес-логики.
Ну и в-третьих, 100%-ное покрытие кода вовсе не гарантирует качества — все зависит от подходов и метрик. Кроме того, функции могут не иметь багов, и быть отлично протестированными, но работать некорректно совсем по другим причинам.
Покрытие кода — полезная вещь, но не главная цель. Главное — это имплементация функциональности приложения согласно требованиям.
Инструменты
Инструменты оценки покрытия кода на самых популярных ЯП
JavaScript: Istambul
Для JavaScript есть удобный инструмент. Поддерживает ES5 и ES2015+. Интегрируется с фреймворками юнит-тестирования на JS: tap, mocha, AVA. Открытый и бесплатный.
Python: Coverage.py
Для Python есть Coverage.py, тоже открытый и бесплатный. CLI, API, интеграция с тестраннерами.
Java: JaCoCo
Для java-разработчиков. Поддерживается Eclipse Foundation. Легко интегрируется с Gradle, Maven, IntelliJ IDEA, Eclipse. Обширные и детализированные репорты по покрытию кода. Открытые исходники, бесплатный.
.NET: NCover
Решение корпоративного уровня для .NET, мощное и богатое функциями. Поэтому платное и с закрытым кодом.