我们已经提供了一些测试用例,但是这些测试用例的好坏如何评估?是否已经覆盖了所有的函数?函数中的分支以及边界条件是否都已经覆盖?
这就需要通过代码覆盖率进行查看,这里简单介绍其使用方法。
简介
在 GCC 中,提供了一个代码覆盖率的分析工具 gcov
,除此之外,还可以使用 lcov
genhtml
等工具生成最终的展示页面,不同发行版本可以通过如下命令安装。
----- Debain
# apt install lcov
----- CentOS
# yum install --enablerepo=epel lcov
使用
在使用 gcc
或者 g++
后面添加参数 --fprofile-arcs
--ftest-coverage
,必须同时在编译器和链接器上设置,在 Makefile
里面可以加在 CFLAGS
和 LDFLAGS
上。
编译完成之后,会生成相关的 *.gcno
文件,运行之后生成 *.gcda
文件,然后可以使用 gcov *.c
生成 *.c.gcov
代码覆盖信息,参数如下。
不过这个是文本,可以将生成文件 main.c.gcov
通过编辑器打开,会发现在代码前有很多的符号标记,简介如下:
#####
没有被执行过。-
不计入统计。N
被调用执行的次数。
文件文件观察起来不太方便,可以通过 lcov
生成图形化的代码覆盖数据,此时会扫描当前目录下所有的相关文件,可以通过 --remove
参数去除某个文件,例如 --remove '/usr/lib/*'
。
$ lcov -d . -t 'Main Test' -o 'main_test.info' -b . -c
会生成一个 main_test.info
文件,接着可以通过 gethtml
生成 html 图形文件,
$ genhtml -o main_test.html main_test.info
然后在浏览器中打开 index 文件即可。
示例
/* main.c */
#include <stdio.h>
#include <stdlib.h>
void test(int count);
int main(int argc, char *argv[])
{
int i = 10;
if(argc == 2)
i = atoi(argv[1]);
printf("arg is %d\n", i);
test(i);
return 0;
}
/* test.c */
#include <stdio.h>
void test(int count)
{
int i;
for (i = 1; i < count; i++) {
if (i % 3 == 0)
printf ("%d is divisible by 3\n", i);
if (i % 11 == 0)
printf ("%d is divisible by 11\n", i);
if (i % 13 == 0)
printf ("%d is divisible by 13\n", i);
}
}
# Makefile
GCOV_FLAGS=-fprofile-arcs -ftest-coverage
CFLAGS+=-g $(GCOV_FLAGS)
#LDFLAGS+=$(GCOV_FLAGS)
target=main
all : $(target)
main : test.o main.o
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
%.o : %.c
$(CC) -c $^ -o $@ $(CFLAGS) $(DEFINES)
.PHONY : clean
clean :
rm -rf *.o
rm -rf $(target)
rm -rf *.gcov *.gcda *.gcno
如果连接的时候出现 undefined reference to '__gcov_init'
错误,则还要加上 -lgocv
。
其它
数据后处理
最常见的是一些三方库不需要统计覆盖率信息,或者只需要统计某些文件的覆盖率,就需要对输出的结果进行筛选。
----- 查看覆盖率统计信息
$ lcov --list foobar.info
----- 移除指定目录的覆盖率统计信息
$ lcov --remove foobar.info '/src/include/*' '/user/bin/*' -o foobar.info.cleaned
----- 只保留固定目录的统计信息
$ lcov --extract foobar.info '*/src/*' '*/lib/*' -o foobar.info.reserved
注意,在 CMake 中上述使用的是绝对路径,这就意味着,如果需要删除测试用例的覆盖,应使用 */tests/*
。
另外,在测试时,有时候会有些脏数据,可以通过如下方式删除,然后重新生成。
find -name '*.gcda' -exec rm -rf {} \;
CMake
暂时没有看到 CMake 直接支持覆盖率测试,需要添加一个三方模块,可以参考 GitHub 或者 本地,这里以本地的为例。将 CodeCoverage.cmake
复制到 CMake 标准目录下,例如 /usr/share/cmake-X.YY/Modules
,或者增加如下配置项。
IF(${WITH_CONVERAGE})
# 指定CodeCoverage.cmake所在路径
SET(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/contrib/cmake)
INCLUDE(CodeCoverage)
SET(COVERAGE_EXCLUDES "'${CMAKE_SOURCE_DIR}/deamon/tests/*'" "'${CMAKE_SOURCE_DIR}/libs/libmock/*'")
SETUP_TARGET_FOR_COVERAGE(coverage ctest covdir)
ENDIF()
然后,通过如下方式编译,通过 make coverage
生成报告,保存在 covdir
目录下。
$ cmake .. -DWITH_UNIT_TESTS=ON -DWITH_CONVERAGE=ON -DCMAKE_BUILD_TYPE=Debug
最后通过一个简单的 HTTP 服务器,用来查看覆盖率信息。
----- python2使用
$ python -m SimpleHTTPServer 8080
----- python3使用
$ python3 -m http.server -d covdir 8080
上述的命令无法指定静态文件的目录,所以需要先切换到相应的目录,然后在执行上述的命令。最后访问 localhost:8080 即可以看到报告。