最近做了一系列的单元测试相关的工作,除了各种规范及测试框架以外,讨论比较多的就是关于代码覆盖率的产生,c/c++与其他的一些高级语言或者脚本语言相比较而言,例如 Java、.Net和php/python/perl/shell等,由于没有这些高级语言和脚本语言的反射的特性,其代码覆盖率的产生过程会稍微复杂一些。发现许多同学对C++的覆盖率如何产生在都不太清楚,这里做一个简单的介绍。
一、基本使用方法
在Linux上的c/c++开发一般都使用gcc/g++作为主要的编译器,如果需要产生覆盖率数据需要在Makefile或者Scons文件中做下面的编译链接设置,
编译的时候,增加 -fprofile-arcs -ftest-coverage 或者 –coverage;
链接的时候,增加 -fprofile-arcs 或者 –lgcov;
打开–g3 选项,去掉-O2以上级别的代码优化选项;否则编译器会对代码做一些优化,例如行合并,从而影响行覆盖率结果;
基本要求就上面三点,但有一个建议,为了上述几个编译选项的使用不影响到正常的编译过程(否则会极大地影响程序的运行效率)。在使用makefile中通过参数传递来支持覆盖率产生,可以在makefile使用下面的方式,
ifeq ($(coverage), yes)
CXXFLAGS += -fprofile-arcs -ftest-coverage
LINKERCXX += -fprofile-arcs -ftest-coverage
OPT_FLAGS = -g3
endif
这样,可以使用 make coverage=yes 来引入这些编译选项而不会影响到正常的编译(scons同理)。
二、简单示例
这里写了一个简单的程序做测试,主要包含三个文件:Rectangle.cpp, RectangleTest.cpp, Makefile。
1)Rectangle.cpp 是被测代码,里面定义了一个简单的类Rectangle(长方形),里面有三个方法:
set_values(),设置长方形对象的长和宽;
area(),求长方形的面积;
lenth(),求长放形的周长;
2)RectangleTest.cpp 是一个简单的测试程序,为了demo使用,并没有使用cppunit/gtest这样的单元测试框架,直接使用了main()函数来调用Rectangle里面的方法;
Rectangle.cpp和RectangleTest.cpp的代码如下图,
3)Makefile比较简单,主要支持在coverage=yes的参数支持。 可以使用-fprofile-arcs -ftest-coverage 选项,这里为了简化使用了 –coverage。
覆盖率产生的过程如下面四个步骤所示,其中步骤3和4,根据需要使用其中一种即可。
1. 编译链接带覆盖率参数的源代码;
2. 运行测试程序;
3. 使用gcov获取文本形式的覆盖率数据;
4. 使用lcov获取html形式的覆盖率数据;
下面针对本例,做这一过程的逐步演示。
1. 编译链接带覆盖率参数的源代码;
由于Makeifle中已经支持了coverage=yes选项,直接运行 “make coverage=yes”,这个时候会产生测试程序,并同时生成gcno文件(关于gcno文件的详细解释,参见第三部分背后原理),如下图,
2. 运行测试程序;
运行./RectangleTest 测试程序,运行结束后,会针对所有的cpp源代码文件产生相应的*.gcda文件(关于gcda文件的详细解释,参见第三部分背后原理),如下图
3. 使用gcov获取文本形式的覆盖率数据;
需要注意的是,这个步骤不是必须的,如果需要文本格式(*.gcov)的覆盖率结果,可是走这个步骤。如果想看html格式的结果,直接跳过这一步骤。gcov是gcc自带的覆盖率结果产生工具,无需单独安装。
针对某个源代码文件,例如 Rectangle.cpp,执行”gcov Rectangle.cpp” 会产生Rectangle.cpp.gcov文件。
这是一个存文本文件,可以通过vim打开,看到详细的行覆盖率数据,如下
4. 使用lcov获取html形式的覆盖率数据;
有些时候需要使用html结果的数据展示,这样看起来更加直观一些。IBM开源了lcov这个工具,更多参见 http://ltp.sourceforge.net/coverage/lcov.php
工具使用,如下图,
手动把cc_result目录拷贝到http/apache等服务器的htdocs目录下,可以通过浏览器来查看覆盖率结果,如下,
整个覆盖率生成的流程按照上面四个步骤就可以搞定。下面一节对其原理做简单的阐述。
三、基本原理
1. 术语解释
在了解背后原理之前,需要对覆盖率技术的一些概念有简单的了解。主要是基本块(Basic Block),基本块图(Basic Block Graph),行覆盖率(line coverage), 分支覆盖率(branch coverage)等。
基本块(Basic Block),”A basic block is a sequence of instructions with only entry and only one exit. If any one of the instructions are executed, they will all be executed, and in sequence from first to last.” 这里可以把基本块看成一行整体的代码,基本块内的代码是线性的,要不全部运行,要不都不运行;