阿里云备案 网站服务内容,陕西 网站建设 陕ICP,网站建设费摊销年限,wordpress 中国地图最近做了一系列的单元测试相关的工作#xff0c;除了各种规范及测试框架以外#xff0c;讨论比较多的就是关于代码覆盖率的产生#xff0c;c/c与其他的一些高级语言或者脚本语言相比较而言#xff0c;例如 Java、.Net和php/python/perl/shell等#xff0c;由于没有这些高级…最近做了一系列的单元测试相关的工作除了各种规范及测试框架以外讨论比较多的就是关于代码覆盖率的产生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 coverageyes 来引入这些编译选项而不会影响到正常的编译scons同理。 二、简单示例 这里写了一个简单的程序做测试主要包含三个文件Rectangle.cpp, RectangleTest.cpp, Makefile。 1Rectangle.cpp 是被测代码里面定义了一个简单的类Rectangle长方形里面有三个方法 set_values()设置长方形对象的长和宽area()求长方形的面积lenth()求长放形的周长 2RectangleTest.cpp 是一个简单的测试程序为了demo使用并没有使用cppunit/gtest这样的单元测试框架直接使用了main()函数来调用Rectangle里面的方法 Rectangle.cpp和RectangleTest.cpp的代码如下图 3Makefile比较简单主要支持在coverageyes的参数支持。 可以使用-fprofile-arcs -ftest-coverage 选项这里为了简化使用了 –coverage。 覆盖率产生的过程如下面四个步骤所示其中步骤3和4根据需要使用其中一种即可。 1. 编译链接带覆盖率参数的源代码 2. 运行测试程序 3. 使用gcov获取文本形式的覆盖率数据 4. 使用lcov获取html形式的覆盖率数据 下面针对本例做这一过程的逐步演示。 1. 编译链接带覆盖率参数的源代码 由于Makeifle中已经支持了coverageyes选项直接运行 “make coverageyes”这个时候会产生测试程序并同时生成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.” 这里可以把基本块看成一行整体的代码基本块内的代码是线性的要不全部运行要不都不运行基本块图Basic Block Graph基本块的最后一条语句一般都要跳转否则后面一条语句也会被计算为基本块的一部分。 如果跳转语句是有条件的就产生了一个分支(arc)该基本块就有两个基本块作为目的地。如果把每个基本块当作一个节点那么一个函数中的所有基本块就构成了一个有向图称之为基本块图(Basic Block Graph)。且只要知道图中部分BB或arc的执行次数就可以推算出所有的BB和所有的arc的执行次数打桩意思是在有效的基本块之间增加计数器计算该基本块被运行的次数打桩的位置都是在基本块图的有效边上行覆盖率line coverage源代码有效行数与被执行的代码行的比率分支覆盖率branch coverage有判定语句的地方都会出现2个分支整个程序经过的分支与所有分支的比率是分支覆盖率。注意与条件覆盖率(condition coverage)有细微差别条件覆盖率在判定语句的组合上有更细的划分。 2. gcc/g 编译选项 gcc需要静态注入目标程序编译选项在编译链接的时候加入2个选项(-ftest-coverage -fprofile-arcs )编译结束之后会生成 *.gcno 文件而经过静态注入的目标程序在“正常结束”后会在运行目录下产生*.gcda数据文件通过gcov工具就可产生覆盖率数据结果。 -ftest-coverage Produce a notes file that the gcov code-coverage utility (see gcov—a Test Coverage Program) can use to show program coverage. Each source file’s note file is called auxname.gcno. Refer to the -fprofile-arcs option above for a description of auxname and instructions on how to generate test coverage data. Coverage data matches the source files more closely if you do not optimize. 让编译器生成与源代码同名的.gcno文件note file这种文件含有重建基本块依赖图和将源代码关联至基本块的必要信息 -fprofile-arcs Add code so that program flow arcs are instrumented. During execution the program records how many times each branch and call is executed and how many times it is taken or returns. When the compiled program exits it saves this data to a file called auxname.gcda for each source file. The data may be used for profile-directed optimizations (-fbranch-probabilities), or for test coverage analysis (-ftest-coverage). Each object file’s auxname is generated from the name of the output file, if explicitly specified and it is not the final executable, otherwise it is the basename of the source file. In both cases any suffix is removed (e.g. foo.gcda for input file dir/foo.c, ordir/foo.gcda for output file specified as -o dir/foo.o). See Cross-profiling. 让编译器静态注入对每个源代码行关联的计数器进行操作的代码并在链接阶段链入经态度libgcov.a其中包含在程序正常结束时生成*.gcda文件的逻辑 下面通过源码解析来说明到底这2个选项做了什么。通过g -S选项产生汇编语言Rectangle.s 和 Rectangle_cc.s (增加–coverage选项命令如下 g -c -o Rectangle.s Rectangle.cpp -g -Wall -S g -c -o Rectangle_cc.s Rectangle.cpp -g -Wall –coverage -S vimdiff Rectangle.s 和 Rectangle_cc.s如下图 通过这样汇编语言的对比可以看出gcc通过这2个参数把打桩的过程完成了。 更深入的内容例如如果想知道gcno/gcda文件的格式可以参考 livelylittlefish 的一篇文章GCC Coverage代码分析-.gcda/.gcno文件及其格式分析http://blog.csdn.net/livelylittlefish/article/details/6448885。 四、扩展话题 通过上面三部分的介绍相信绝大多数覆盖率问题都可以解决下面2个问题是我们在实际运行过程中遇到的也分享一下。 覆盖率的结果只有被测试到的文件会被显示并非所有被编译的代码都被作为覆盖率的分母 实际上可以看到整个覆盖率的产生的过程是4个步骤的流程一般都通过外围脚本或者makefile/shell/python来把整个过程自动化。2个思路去解决这个问题都是通过外围的伪装。第一个就是修改lcov的 app.info 中间文件找到其他的文件与覆盖率信息的地方结合makefile把所有被编译过的源程序检查是否存于 app.info 中如果没有增加进去。第二个伪装是伪装 *.gcda没有一些源码覆盖率信息的原因就是该文件没有被调用到没有响应的gcda文件产生。toasthttp://toast.taobao.org/是通过第一种伪装来实现的更多了解需要去看下开源代码。 2. 后台进程的覆盖率数据收集 其实上述覆盖率信息的产生不仅可以针对单元测试对于功能测试同样适用。但功能测试一般linux下c/c都是实现了某个Daemon进程而覆盖率产生的条件是程序需要正常退出即用户代码调用 exit 正常结束时gcov_exit 函数才得到调用其继续调用 __gcov_flush 函数输出统计数据到 *.gcda 文件中。同样2个思路可以解决这个问题 第一给被测程序增加一个 signal handler拦截 SIGHUP、SIGINT、SIGQUIT、SIGTERM 等常见强制退出信号并在 signal handler 中主动调用 exit 或 __gcov_flush 函数输出统计结果。但这个需要修改被测程序。这个也是我们之前的通用做法。但参加过清无同学的一个讲座后发现了下面第二种更好的方法。 第二借用动态库预加载技术和 gcc 扩展的 constructor 属性我们可以将 signalhandler 和其注册过程都封装到一个独立的动态库中并在预加载动态库时实现信号拦截注册。这样就可以简单地通过如下命令行来实现异常退出时的统计结果输出了。 五、其他编程语言 在我们的工程实践中还有其他的编程语言都涉及到覆盖率的产生我们的工程实践推荐下面的方法 c/c, 本文介绍的方法Java, Maven cobertura 插件Python, PyUnit coverage.pyPhp, phpunit –coverage-html ;Perl, Test::Class 和 Devel::Cover;Shell, shUnit2 shcov;