Linux C program cover rate test
在拥有了一定的测试框架和测试用例后,如何评估测试的覆盖率,全面性就成为了一个问题,这需要一定的工具去帮助我们查看哪些内容还没有被测试到,需要我们定向的增加测试用例。
当然这些工具也可以帮助我们观测到更多内容,比如说每行代码的被执行次数,可以用来作为问题的排查等等。
TLDR
暂无法直接使用,仅为部分提示。
PROGRAM = _debug
gcov:
-g -O0
CFLAGS += -fprofile-arcs -ftest-coverage
LDFLAGS += -lgcov -coverage
debug:all
-g
cp ./$(BUILD_BIN) ./bin/$(BIN)
背景
首先在拥有了一定的测试框架和测试用例后,评估测试的覆盖率,全面性就成为了一个问题,需要一定的工具去帮助我们查看哪些内容还没有被测试到,需要我们增加测试用例。
当然工具也可以看到其他的内容,比如说每行代码的被执行次数。
以下内容涉及 gcov 以及其他的工具比如 lcov 等等
同时本程序 在 Linux host 主机上完成交叉编译,在 linux arm 平台运行,如果编译运行在同一设备,则速度更快。
GCOV
GCC 官方文档中对于 GCOV 的描述详见连接 Cross-profiling (GCC)
在 Linux 平台软件的 测试覆盖率测试 不得不提及 GCOV,这也是 GCC 内置的代码执行覆盖率测试方案。
运行原理
- 在 GCC 编译时加入了特殊的编译选项,生成可执行文件,和
*.gcno; - 运行(测试)生成的可执行文件(非异常退出),生成了
*.gcda数据文件; - 通过
*.gcno和*.gcda,生成gcov文件,最后通过前端展示工具LCOV和GCOVR生成代码覆盖率报告。
当然 更详细的原理还包括了.gcno和.gcda两个文件的含义即生成他们的过程,可以在 Cross-profiling (GCC) 看到更加详细的内容。这里简单阐述一下:
.gcno 是由增加 -ftest-coverage 编译参数所产生的,它包含了重建基本块图和相应的块的源码的行号的信息。.gcda 是由增加 -fprofile-arcs 编译参数后的二进制文件完整运行所产生的,它包含了弧跳变的次数和其他的概要信息( gcda 只能在程序在对应平台完整运行后,才能产生)。
使用方案
目前最新的 GCC 可能只需要一个 类似于 -coverage 的选项 就可以实现了。不过在相对旧一些的 GCC 版本中
编译
GCC 编译的时候添加 -fprofile-arcs -ftest-coverage -g -O0 选项,链接时 添加 -lgcov -coverage
两者未必需要这么多,只是这样相对全面
-fprofile-arcs 解释参考来源
参数使 gcc 创建一个程序的流图,之后找到适合图的生成树。
只有不在生成树中的弧被操纵(instrumented):
gcc 添加了代码来清点这些弧执行的次数。
当这段弧是一个块的唯一出口或入口时,操纵工具代码(instrumentation code)将会添加到块中,否则创建一个基础块来包含操纵工具代码。
编译完成后,可以在目录中查找到 二进制可执行文件,和 *.gcno 文件,一一对应。
执行
在执行设备上进行 执行程序,对于交叉编译的方案,可以将程序传输至设备中,运行。本地方案直接运行即可。
注意,在下一步中,gcda 和 gcno 需要在同一个目录下,假如在编译机器上面是 test/path,编译的时候会在这个目录下生成.gcno 文件,那么在执行机器上也会生成.gcda 文件在 test/path 目录下,
交叉编译方案运行时 ,请注意 至少提前配置环境变量 GCOV_PREFIX
可以设定为当前目录,之后再对生成的 .gcda 文件(夹)进行转移。
因为在正常情况下,HOST 编译,运行 在同一位置,直接运行可以使得生成的 .gcda 文件放在了同一个文件夹下。但是如果是交叉编译,那么之前 HOST 主机上的路径在下端设备上是不存在的,故而可能会将内容放到很奇特的路径下,如果没有相关的写权限,还可能无法创建,为了确保不影响根目录和方便查找,我们提前设置为当前目录或其他方便的目录,给生成的文件(夹)增加一个前缀,相当于 指定输出路径。
export GCOV_PREFIX=$PWD而我们 也可以指定
GCOV_PREFIX_STRIP 环境变量,GCOV_PREFIX_STRIP 消除Linux 交叉编译使用代码覆盖GCOV及LCOV
export GCOV_PREFIX_STRIP=8.gcda 存放目录层次(从顶级目录开始消除)。这个可以在生成相关信息后,我们通过查看路径层次,然后再去优化。
展示
接下来我们就需要将 下端生成的 .gcda 文件复制到 host 设备的原路径下,注意 .gcda 和 同名的 .gcno 文件应该在同一目录。并且有源代码,且源代码的目录和编译时的目录一样,否则不能生成。
也可以直接将 整个目录的内容给进行覆盖。
接下来就需要用到 LCOV 和 GCOVR 进行可视化,数据化展示了。
LCOV
# 安装lcov,包含 genhtml
sudo apt install lcov
lcov -c -d ./ -o all.info --gcov-tool=/opt/crossCompileTools/arm-2008q1/bin/arm-none-linux-gnueabi-gcov
标识 生成以当前目录下的 所有内容(附带递归) 通过 交叉编译器的 GCOV 进行整合生成 all.info 文件。可以通过使用多个-d以添加多个目录
—directory 或者-d 表示的是目录,也就是 gcno 和 gcda 目录
—capture 或者 -c 表示获取覆盖率信息
—output-file 或者 -o 表示输出文件
接下来,我们可以 进行过滤 相关内容(当然也可以不过滤)lcov收集覆盖率过滤内容
# 过滤掉不需要统计的目录
lcov -r all.info "/usr/include/*" "gtest_utils*" -o result.info在某些情况下,比如三方库不需要统计覆盖率信息,则需要屏蔽,或者只需要某些文件的覆盖率,就需要对文件进行筛选
a)移除指定目录
lcov --remove all.info '/src/include/*' '/user/bin/*' -o result.info此命令表示生成的覆盖率信息,屏蔽
'/src/include/*' '/user/bin/*' 两个目录的覆盖率信息all.info 总的覆盖率信息
result.info 筛选后的覆盖率信息
b)只要固定目录
lcov --extract all.info '*/src/*' '*/lib/*' -o result.info此命令表示生成的覆盖率信息只要src和lib目录下文件的覆盖率信息
all.info 总的覆盖率信息result.info 筛选后的覆盖率信息
最后,通过 genhtml 生成可视化的 HTML 文件genhtml -o results app.info
results 是一个目录。
GCOVR
GCOVR 是一个简短的通过 Python 完成的程序,因此我们可以通过 pip 进行安装。
但是 在一些老旧的设备上 Python 版本过低,可能无法运行,因此可以使用sudo apt install gcovr 进行安装(环境 Ubuntu)
gcovr -r . --gcov-executable=/opt/crossCompileTools/arm-2008q1/bin/arm-none-linux-gnueabi-gcov --html-details -o ./gcovr/gcovdetail.html
指定了 交叉编译的 gcov 版本,表示在 . 路径下,递归生成详细的 HTML 文件,通过-o 指定 输出结果位置。
还可以追加 -s 进行展示一个简短的结果。
两者虽然都不错,GCOVR 似乎更加简洁,生成的界面增加了 branch 的统计,但是看代码样式并不是很完美。而 LCOV 虽然复杂了一点点,但是界面相对美观,同时能够将代码展示在一行中,也是很不错的工具。
两者可以交叉使用。
注意
如果没有生成 *.gcda 数据文件,可能是程序异常(手动)终止,
或 gcov 版本和编译器的不一致。
因此在测试时,请及时退出程序或增加 __gcov_flush() 函数
如果程序为异常退出,则不会生成文件,此时需要在代码适当位置插入 __gcov_flush() 函数,将文件进行保存(未测试此方案,建议单元测试结束后,直接结束程序)。