Все статьи Code Coverage средствами GCC
В этой статье мы освоим применение gcov и lcov для подсчёта line-based и branch-based покрытия кода для C/C++
GCC - это GNU Compilers Collection, набор компиляторов проекта GNU. Прежде всего это компиляторы C/C++ (
gcc
иg++
). GCC входит в тройку лидирующих C/C++ компиляторов: GCC, Clang, MSVC.
Установка gcov и lcov на Linux
Вместе с GCC распространяется утилита командной строки “gcov”. Если у вас есть установленный GCC, то и gcov тоже есть. Проверить можно консольной командой, попросив gcov распечатать свою версию:
gcov --version
Утилита “lcov” (графический интерфейс для gcov) устанавливается отдельно. В Ubuntu/Debian установить можно с помощью apt. Затем стоит проверить работоспособность lcov, запустим его в командной строке.
sudo apt install lcov
lcov --version
Установка gcov и lcov на Windows
Вместе с пакетом программ MinGW64 (это порт GCC для Windows) распространяется утилита командной строки “gcov”. Если MinGW64 у вас установлен и добавлен в переменную окружения %PATH%
, то и gcov тоже есть. Проверить можно консольной командой, попросив gcov распечатать свою версию:
gcov --version
Если при запуске в терминале вы получаете сообщение, что программа gcov
не найдена, значит, вам нужно проверить установку
- Убедитесь, что MinGW64 установлен - вы можете загрузить MinGW64 с sourceforge.net
- Убедитесь, что путь к подкаталогу
bin
установленногоMinGW
добавлен в переменную окружения%PATH%
- инструкция по настройке
%PATH%
есть на computerhope.com
- инструкция по настройке
Компилируем мини-программу с анализом Coverage
Создайте файл fizzbuzz.cpp
и скопируйте или перепишите в него код:
#include <iostream>
int main()
{
for (int num = 1; num <= 100; num += 1)
{
if ((num % 3 == 0) && (num % 5 == 0))
{
std::cout << "FizzBuzz" << std::endl;
}
else if (num % 3 == 0)
{
std::cout << "Fizz" << std::endl;
}
else if (num % 5 == 0)
{
std::cout << "Buzz" << std::endl;
}
else
{
std::cout << num << std::endl;
}
}
}
Теперь откройте терминал в каталоге, где находится fizzbuzz.cpp
, и скомпилируйте программу одной командой:
g++ --coverage fizzbuzz.cpp -o fizzbuzz
Если компиляция не удалась, прочитайте текст ошибки и исправьте ошибку. Если всё в порядке, то в каталоге появится ещё два файла: исполняемый файл fizzbuzz
и файл fizzbuzz.gcno
, содержащий базовую информацию для запуска Code Coverage.
Теперь надо запустить программу, чтобы в процессе работы она записала информацию о фактическом покрытии кода на данном запуске. В UNIX и в Windows запуск выглядит по-разному:
# В Windows введите имя файла
fizzbuzz.exe
# В Unix введите относительный путь, начиная с текущего каталога "."
./fizzbuzz
После завершения работы программы вы увидите в каталоге файл fizzbuzz.gcda
, который и содержит информацию о фактическом покрытии кода.
Составляем отчёт о Code Coverage с помощью lcov
После предыдущего шага у нас есть всё, что надо для lcov, и теперь стало намного проще! Введите в терминале команду lcov с несколькими опциями:
lcov -t "fizzbuzz" -o fizzbuzz.info -c -d .
Пояснения по поводу опций:
-t <имя>
устанавливает имя отчёта, при измерении покрытия кода тестами можно указать имя теста или набора тестов-o <имя>
устанавливает имя выходного файла с промежуточной информацией-c
указывает, что lcov должен использовать существующие данные о coverage-d <путь>
устанавливает каталог, в котором надо искать данные о coverage, и мы передаём текущий каталог “.”
Теперь можно сгенерировать отчёт в виде HTML-страницы с помощью утилиты genhtml
, входящей в состав пакета программ lcov:
# -o <путь> задаёт имя каталога, где будет размещён отчёт
genhtml -o report fizzbuzz.info
Теперь вы можете перейти в каталог report
, открыть файл index.html
в браузере и посмотреть отчёт. Вы увидите страницу, на которой показан статичный отчёт о покрытии различных каталогов, и можно перейти по ссылкам для просмотра отдельных каталогов:
Вы можете перейти к конкретному файлу и посмотреть покрытие этого файла:
Отчёт о непокрытых строках
Изменим программу, чтобы в ней появился недостижимый код:
#include <iostream>
int main()
{
for (int num = 1; num <= 100; num += 1)
{
if ((num % 3 == 0) && (num % 5 == 0))
{
std::cout << "FizzBuzz" << std::endl;
}
else if (num % 3 == 0)
{
std::cout << "Fizz" << std::endl;
}
else if (num % 5 == 0)
{
std::cout << "Buzz" << std::endl;
}
else if (num % 75 == 0)
{
std::cout << "Fizz" << std::endl;
}
else
{
std::cout << num << std::endl;
}
}
}
Теперь выполним все команды для компиляции, запуска, сбора данных и сборки отчёта:
g++ --coverage fizzbuzz.cpp -o fizzbuzz
./fizzbuzz
lcov -t "fizzbuzz" -o fizzbuzz.info -c -d .
genhtml -o report fizzbuzz.info
Обновите страницу отчёта в браузере - и вы увидите, что теперь Code Coverage достигает лишь 91%:
Вы можете перейти к файлу и посмотреть, какие именно строки оказались непокрытыми:
Отчёт о покрытии ветвлений
Для получения отчёта с информацией о выборе ветвлений надо добавить параметр --rc lcov_branch_coverage=1
как для вызова lcov
, так и для вызова genhtml
:
lcov -t "fizzbuzz" -o fizzbuzz.info -c -d . --rc lcov_branch_coverage=1
genhtml -o report fizzbuzz.info --rc lcov_branch_coverage=1
Теперь вы можете повторно открыть index.html
и познакомиться с новой информацией. Знаки [- +]
возле else if (num % 75 == 0)
означают, что программа ни разу не зашла в первую ветку (ветку then), но заходила во вторую (ветку else). В остальных ветвлениях if/else программа заходила в разное время в обе ветки.
У любопытных людей может возникнуть вопрос: почему в конце функции main тоже написано [+ -]
? Это правильный вопрос, детектив. Дело в том, что каждая функция может быть завершена либо нормальным способом (вернув что-либо на инструкции return), либо выбросом исключения. Если немного модифицировать программу, написав всегда бросающую исключение функцию ohh
, то branch coverage для вызова ohh()
изменится на [- +]
: функция ни разу не завершилась нормально, но как минимум один раз завершилась с выбросом исключения: