Cmake

Code coverage для Qt-проекта: gcov, lcov и интерпретация отчётов

2 просмотров

Введение

Покрытие кода тестами (code coverage) показывает, какая доля строк/веток кода была выполнена хотя бы раз во время прогона тестового набора. Это не метрика качества тестов само по себе (100% покрытие не означает отсутствие багов), но полезный индикатор того, какие части кода вообще никогда не проверялись. В статье настроим сбор покрытия для Qt-проекта с использованием gcov (встроенного в GCC инструмента) и lcov (для генерации читаемых HTML-отчётов).

Концепция

gcov собирает данные о покрытии, если код скомпилирован с флагами --coverage (включает генерацию инструментированного кода, который во время выполнения записывает счётчики выполнения строк в .gcda-файлы рядом с объектными .gcno-файлами). После выполнения тестов gcov или, что удобнее, lcov обрабатывает эти файлы и генерирует отчёт — текстовый или HTML с построчной разметкой покрытых/непокрытых строк.

Для CMake-проектов флаги покрытия добавляются как опции компилятора и линкера, обычно условно — только для специальной сборки с покрытием, а не для обычной Release/Debug сборки, поскольку инструментация снижает производительность выполнения.

Пример кода

# CMakeLists.txt — опциональная цель с покрытием кода
option(ENABLE_COVERAGE "Включить сбор покрытия кода gcov/lcov" OFF)

if (ENABLE_COVERAGE)
    add_compile_options(--coverage -O0 -g)
    add_link_options(--coverage)
endif()

add_executable(TestCalculator test_calculator.cpp)
target_link_libraries(TestCalculator PRIVATE Qt6::Test)
add_test(NAME TestCalculator COMMAND TestCalculator)
# Сборка с включённым покрытием
cmake -B build-coverage -S . -DENABLE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug
cmake --build build-coverage

# Запуск тестов
cd build-coverage
ctest

# Сбор данных покрытия с помощью lcov
lcov --capture --directory . --output-file coverage.info

# Исключение системных и сторонних заголовков из отчёта
lcov --remove coverage.info '/usr/*' '*/build-coverage/*' --output-file coverage_filtered.info

# Генерация HTML-отчёта
genhtml coverage_filtered.info --output-directory coverage_html

# Открыть coverage_html/index.html в браузере для просмотра отчёта

Пояснения к коду

Опция ENABLE_COVERAGE делает сбор покрытия опциональным — обычная сборка для разработки не несёт накладных расходов инструментации. При включении добавляются флаги --coverage (для компилятора и линкера) и -O0 -g — отключение оптимизаций важно, поскольку агрессивная оптимизация компилятора может слить или удалить строки кода, что искажает построчное соответствие отчёта покрытия исходному коду.

После запуска тестов lcov --capture агрегирует все .gcda-файлы в единый файл coverage.info, который затем фильтруется командой --remove для исключения системных заголовков и самих тестовых файлов из отчёта (покрытие тестов самих себя неинформативно), а genhtml превращает итоговые данные в навигируемый HTML-отчёт с построчной подсветкой.

Подводные камни

  • Включение покрытия в Release-сборке с оптимизациями. Оптимизирующий компилятор может объединять или переупорядочивать инструкции, из-за чего соответствие между строками исходного кода и фактически выполненными инструкциями становится неточным — покрытие почти всегда измеряют на Debug-сборке без оптимизаций.
  • Интерпретация 100% покрытия как доказательства отсутствия багов. Покрытие показывает, что строка была выполнена, но не то, что результат был корректно проверен — тест без единой проверки (QCOMPARE/QVERIFY) может дать высокое покрытие, не находя при этом ни одной реальной ошибки.
  • Покрытие генерируемого кода (moc, uic) искажает общую цифру. Сгенерированные Qt файлы (moc_*.cpp, ui_*.h) обычно не несут смысловой нагрузки для анализа покрытия и должны исключаться из отчёта аналогично системным заголовкам — иначе общий процент покрытия проекта может казаться искусственно ниже или выше реального.
  • Несовпадение версии gcov с версией компилятора GCC. gcov тесно связан с конкретной версией GCC, которой был скомпилирован код — попытка обработать данные покрытия инструментом другой версии может приводить к ошибкам или неверным данным в отчёте.