统计C/C 代码覆盖率的工具很多,比如OpenCppCoverage可以与VS工具配合,获取并展示代码覆盖率简单直观,但是在Linux、Mac等系统该如何统计呢?一般的持续集成工具(Jenkins、gitlab-ci等)中又该如何统计呢?
准备工具
请参考教程安装即可:
- GCC
- CMake
- Google Test
- gcov
- lcov
- gcovr
代码覆盖率
代码覆盖率一般包含以下几种类型:
- 函数覆盖率:描述有多少比例的函数经过了测试。
- 语句覆盖率:描述有多少比例的语句经过了测试。
- 分支覆盖率:描述有多少比例的分支(例如:if-else,case语句)经过了测试。
- 条件覆盖率:描述有多少比例的可能性经过了测试。
因此一般的覆盖率结果也分为几种不同的类型。
gcov
gcov是由gcc工具链提供的代码覆盖率生成工具,可以很方便的和GCC编译器配合使用,通常情况下,直接安装gcc工具链,也就同时包含了gcov命令行工具。
对于代码覆盖率工具所做的工作,可以简单的理解为:标记一次运行过程中,哪些代码被执行过,哪些没有执行。 因此,即便没有测试代码,直接运行编译产物也可以得到代码的覆盖率。只不过,通常情况下这样得到的覆盖率较低罢了。
在项目中我们使用cmake编译,因此在CMakeLists.txt文件中设置覆盖率相关参数。
代码语言:javascript复制SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage")
将-fprofile-arcs -ftest-coverage添加到编译器flag中,这个参数是很重要的,是生成代码覆盖率所必须的,对于该参数的说明可以参考:https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html#Instrumentation-Options
执行cmake、make命令编译之后生成单测可执行文件Test。执行单测:
代码语言:javascript复制./Test --gtest_filter=ClassName.CaseName
单测执行之后,我们会得到每个源码文件对应的gcda和gcno后缀文件,比如main函数所在的主文件TestMain.cpp,通过TestMain.cpp.gcda和TestMain.cpp.gcno两个文件,便可以得到代码TestMain.cpp的覆盖率结果了。
通过gcov指定源码文件的名称,便可以得到该源码文件的覆盖率结果:
代码语言:javascript复制gcov TestMain.cpp.gcno
lcov
gcov得到的结果是文本形式的,而且不同的源码文件需要一一执行gcov命令,对于大工程是不方便的,我们希望得到更加美观和便于浏览的结果。
lcov是gcov工具的图形前端,收集多个源文件的gcov数据,生成描述覆盖率的HTML页面。生成的结果中会包含概述页面,方面浏览。
lcov有很多参数配合使用可以满足各种需求,lcov的使用方法可以通过以下这条命令查询:
代码语言:javascript复制lcov --help
我们一般关注以下这几个参数:
- -c 或者 --capture 指定从编译产物中收集覆盖率信息。
- -d DIR 或者 --directory DIR 指定编译产物的路径。
- -e FILE PATTERN 或者 --extract FILE PATTERN 从指定的文件中根据PATTERN过滤结果。
- -o FILENAME 或者 --output-file FILENAME 指定覆盖率输出的文件名称。
此外,特殊说明:
- lcov默认不会打开分支覆盖率,因此我们还需要增加这个参数来打开分支覆盖率的计算: --rc lcov_branch_coverage=1
- lcov输出的仍然是一个中间产物,我们还需要通过lcov软件包提供的另外一个命令genhtml来生成最终需要的html格式的覆盖率报告文件。 同样的,为了打开分支覆盖率的计算,我们也要为这个命令增加--rc lcov_branch_coverage=1参数
最后,我们编辑一个make_all.sh脚本执行lcov相关操作:
代码语言:javascript复制COVERAGE_FILE=coverage.info
REPORT_FOLDER=coverage_report
lcov --rc lcov_branch_coverage=1 -c -d . -o ${COVERAGE_FILE}_tmp
lcov --rc lcov_branch_coverage=1 -e ${COVERAGE_FILE}_tmp "*src*" -o ${COVERAGE_FILE}
genhtml --rc genhtml_branch_coverage=1 ${COVERAGE_FILE} -o ${REPORT_FOLDER}
代码解析:
我们从前面的编译结果中收集覆盖率结果,并将结果输出到coverage.info_tmp文件中,该文件存储在当前目录下。但是这里面会包含非项目源码的覆盖率(例如GoogleTest),所以我们通过另外一条命令指定"src"文件夹进行过滤。最后,通过genhtml得到HTML格式的报告,报告结果存储在文件夹coverage_report中。
gcovr
一般场景下使用gcov和lcov能满足代码覆盖率的获取和展示工作,lcov和genhtml配合生成的HTML报告内容详尽,简洁直观,行覆盖率、分支覆盖率都有,但是HTML文件在常用的持续集成工具(比如Jenkins、gitlab-ci)中均无法集成,因此我们需要其他的工具用于覆盖率结果的持续集成展示。
gcovr是一款针对C/C 代码覆盖率并支持以多种方式(包括列表方式、XML文件方式、HTML网页方式等)展示出来的工具,而XML文件刚好是可以被持续集成工具解析的。
gcovr有很多参数配合使用可以满足各种需求,gcovr的使用方法可以通过以下这条命令查询:
代码语言:javascript复制gcovr --help
我们一般关注以下这几个参数:
- -r ROOT 或者 --root ROOT 代码根目录,默认为'.',当前的路径。
- -b 或者 --branches 以分支覆盖率形式报告。
- -x 或者 --xml 指定报告的形式为XML。
- -o OUTPUT 或者 --output OUTPUT 指定覆盖率输出的文件名称。
- --html 指定报告的形式为HTML。
在项目的编译根目录下使用如下命令:
代码语言:javascript复制gcovr -r . --xml -o coverage.xml
当前目录下生成coverage.xml文件详细记录了所有源码文件的行覆盖率信息。
常见问题:gcovr得到的覆盖率为0%
解决:执行gcovr -r . 命令一般在编译路径下,cmake项目中我们一般习惯创建一个build文件夹编译源文件,测试执行之后,build路径下包含gcda和gcno、cpp.o,其实执行gcovr命令还需要源码文件,因此,需要在上层根目录下执行gcovr。