суббота, 7 апреля 2012 г.

Покрытие кода с gcov и gcovr

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

  • Как часто (количество прохождений) выполняется каждая строка кода.
  • Какие строки кода не выполняются.
  • Какой процент строк кода покрыт тестами для каждого файла.
  • Какой процент ветвлений (условий) покрыт тестами для каждого файла.

Использование утилиты gcov для анализа кода, требует наличия при компиляции флагов -fprofile-arcs -ftest-coverage, которые сообщают компилятору о необходимости дополнительной информацию требуемой для gcov (граф прохождения программы), а также о необходимости включить дополнительные инструкции кода непосредственно в объектные файлы, также необходимых для gcov.

В общем и простейшем случае процедура запуска компиляции следующая:
$ g++ -fprofile-arcs -ftest-coverage main.cpp

Рассмотрим использование gcov на чуть более сложном примере. Пусть в нашем распоряжении имеется тестируемый проект, который состоит:

  • main.cpp – в котором расположена инструкция запуска тестов.
  • gtest/ – каталог, в котором находится исходный код тестовой среды Google Test.
  • SRM204/ – каталог, в котором находятся заголовки с решениями и тестами задач.

В таком случае процесс запуска компиляции проекта с последующей возможностью анализа покрытия кода тестами будет выглядеть следующим образом:
$ mkdir Debug && cd Debug/
$ g++ -I.. -I../SRM204 -c -fprofile-arcs -ftest-coverage -o "main.o" "../main.cpp"
$ g++ -I.. -I../SRM204 -c -fprofile-arcs -ftest-coverage -o "gtest-all.o" "../gtest/gtest-all.cc"
$ g++ -fprofile-arcs -ftest-coverage -o "TopCoder.exe"  ./gtest-all.o  ./main.o

После успешной компиляции необходимо запустить полученный исполняемый файл, в ходе исполнения которого будут сгенерированы необходимые утилите gcov вспомогательные для последующего анализа:
$ ./TopCoder.exe

Далее, используя утилиту gcov, получим общие сведения о покрытии кода тестами, а также отчеты по покрытию кода для каждого файла проекта в отдельности, которые имеют расширение *.gcov:
$ gcov -b ../main.cpp

Фрагмент сведений выводимых, утилитой gcov в ходе формирования отчетов по каждому используемому файлу (с исходным кодом) в ходе исполнения работы тестируемой программы:
File '../SRM204/Apothecary.h'
Lines executed:100.00% of 67
Branches executed:62.16% of 222
Taken at least once:36.94% of 222
Calls executed:49.14% of 175
../SRM204/Apothecary.h:creating 'Apothecary.h.gcov'

Из представленного фрагмента, очевидно, что покрытие кода, в данном файле, составляет 100%, при этом вероятность прохождения всех веток составляет 62%.

Не стоит удивляться, если в ходе «профайлинга» будет представлена информация о покрытии кода подключаемых заголовков стандартной библиотеки C/C++:
$ ls . | grep ".gcov$"
...   ...   ...
new.gcov
new_allocator.h.gcov
ostream.gcov
sstream.gcov
...   ...   ...

Ниже представлен фрагмент сгенерированного отчета утилитой gcov по файлу с исходным кодом:
98:   77: for (int j = 0; j < (int) inputs[i].length(); j++) {
91:   78:    iter = elem->find(inputs[i][j]);
91:   79:    if (iter == elem->end()) {
73:   80:       if (inputs[i][j] != ' ') {
 -:   81:          // ... ...
 -:   82:       }
 -:   83:    }
 -:   84:    else {
18:   85:       iter->second++;
 -:   86:    }
 -:   87: }
Значения, расположенные слева отражают сколько раз была исполнена та или иная строка кода. В данном конкретном случае, очевидно, что строка с номером 85 была выполнена в ходе тестирования 18 раз, а 81-ая строка – 65 раз соответственно.

Сама по себе утилита gcov не предоставляет никаких возможностей по фильтрации тех исходных файлов, которые не тестируются или вообще не относятся к проекту (например, исходные коды стандартной библиотеки C/C++), кроме того, нет никаких возможностей по резюмированию и протоколированию результатов. Однако существует утилита gcovr, которая расширяет возможности gcov и представляет собой скрипт на языке Python, который решает обозначенные выше проблемы утилиты gcov.

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

Поскольку синтаксис использования утилиты gcov не представляет из себя что-то из ряда вон выходящего, то перейдем непосредственно к рассмотрению примера работы с рассматриваемой утилиты. Запросим резюме на покрытие тестами кода в исходных файлах корневого каталога данного проекта (опция -r позволяет указать каталог, исходные файлы которого, в том числе и в нижележащих каталогах, будут анализироваться на предмет покрытия), за исключением каталога gtest/, в котором расположены исходные коды Google Test (опция -e позволяет указать, исходные файлы каких каталогов не подвергать анализу):
$ gcovr -r ../ -e '..*/gtest/'
------------------------------------------------------------------------------
File                                       Lines    Exec  Cover   Missing
------------------------------------------------------------------------------
SRM204/Aaagmnrs.h                             67      65    97%   44-45
SRM204/Apothecary.h                           67      67   100%
SRM204/Medici.h                               58      58   100%
main.cpp                                       4       4   100%
------------------------------------------------------------------------------
TOTAL                                        196     194    98%
------------------------------------------------------------------------------

Результат, выводимый утилитой на консоль, при отсутствии флага исключения (-e --exclude) включал бы в себя сведения о покрытии кода самой тестовой среды Google Test.

Для того, чтобы получить отчет по каждому файлу, как в gcov, необходимо использовать опцию -k --keep, поскольку по-умолчанию gcovr удаляет все отчеты:
$ gcovr -k -r ../ -e '..*/gtest/'

Если необходимо определить покрытие кода не по строкам, а по ветвям, достаточно указать соответствующую опцию -b --branches:
$ gcovr -b -r ../ -e '..*/gtest/'
------------------------------------------------------------------------------
File                                      Branch   Taken  Cover   Missing
------------------------------------------------------------------------------
SRM204/Aaagmnrs.h                            180      99    55%   33,34, ...
SRM204/Apothecary.h                          138      82    59%   67,74, ...
SRM204/Medici.h                              138      76    55%   30,49, ...
main.cpp                                       4       3    75%   31
------------------------------------------------------------------------------
TOTAL                                        460     260    56%
------------------------------------------------------------------------------

Но наиболее яркой особенностью утилиты gcovr является генерация отчетов о покрытии кода тестами в формате XML, пригодных для отображения результатов покрытия в таких сервисах, как Jenkins и Hudson:
$ gcovr -r ../ -e '..*/gtest/' -x > coverage-report.xml

Таким образом, использование утилит gcov и gcovr позволяет в полном объеме оценить в процентном соотношении покрытие кода тестами, а при соответствующей интеграции с сервисами Jenkins и Hudson отследить динамику изменений.