Linux下c/c++项目代码覆盖率的产生方法

最近做了一系列的单元测试相关的工作,除了各种规范及测试框架以外,讨论比较多的就是关于代码覆盖率的产生,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.”  这里可以把基本块看成一行整体的代码,基本块内的代码是线性的,要不全部运行,要不都不运行;
  • 基本块图(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个问题是我们在实际运行过程中遇到的,也分享一下。

  1. 覆盖率的结果只有被测试到的文件会被显示,并非所有被编译的代码都被作为覆盖率的分母

实际上,可以看到整个覆盖率的产生的过程是4个步骤的流程,一般都通过外围脚本,或者makefile/shell/python来把整个过程自动化。2个思路去解决这个问题,都是通过外围的伪装。第一个,就是修改lcov的 app.info ,中间文件,找到其他的文件与覆盖率信息的地方,结合makefile,把所有被编译过的源程序检查是否存于 app.info 中,如果没有,增加进去。第二个伪装,是伪装 *.gcda,没有一些源码覆盖率信息的原因就是该文件没有被调用到,没有响应的gcda文件产生。toast(http://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.py;
  • Php, phpunit +  –coverage-html ;
  • Perl,  Test::Class 和 Devel::Cover;
  • Shell,  shUnit2 + shcov;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/287445.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C# WPF从后台代码生成行列可变的表格

z概述WPF常用的表格控件是DataGrid,这个控件在前台XAML编写的话,一般列已经固定,然后给每个列去绑定数据,但是如果我的列不固定,随着运算结果变动呢?这时候DataGrid,就比较难实现这个需求&#…

软件架构实践文章链接

2019独角兽企业重金招聘Python工程师标准>>> 架构 InfoQ: 又拍网架构中的分库设计 SNS网站数据库技术分析 - 51CTO.COM 数据库水平切分的实现原理解析 - iBATIS - Java - JavaEye论坛 基于amoeba的mysql分布式数据库学习(一) - Java - JavaEy…

数据库SQL语句学习笔记(6)-使用函数处理数据

1.SQL也可以用函数来处理数据,函数一般是在数据上执行的,为数据的转换和处理提供了方便。但是每一个数据库管理系统(DBMS)都有特定的函数,事实上,只有少数几个函数被所有的DBMS等同地支持。例如&#xff0c…

【遥感物候】Matlab求解一元六次多项式,计算植被生长季始期

一元六次多项式能很好的逼近滤波后的曲线,与二次多项式相比,在拟合植被整个生长季曲线方面有更好的优势,该方法常用来描述北方温带和高纬度地区时序NDVI生长季模式。因此,本文使用一元六次多项式来拟合植被整个生长季曲线,效果很好。那么拟合后,这样解方程呢求生长季参数…

最全面透彻的RabbitMQ指南

概念RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用Erlang语言编写的,而集群和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯…

TensorFlow中RNN实现的正确打开方式

上周写的文章《完全图解RNN、RNN变体、Seq2Seq、Attention机制》介绍了一下RNN的几种结构,今天就来聊一聊如何在TensorFlow中实现这些结构,这篇文章的主要内容为: 一个完整的、循序渐进的学习TensorFlow中RNN实现的方法。这个学习路径的曲线较…

《看聊天记录都学不会C语言?太菜了吧》(2)我说编程很容易你们不服?

若是大一学子或者是真心想学习刚入门的小伙伴可以私聊我,若你是真心学习可以送你书籍,指导你学习,给予你目标方向的学习路线,无套路,博客为证。 本系列文章将会以通俗易懂的对话方式进行教学,对话中将涵盖…

LCD1602,4位数据总线液晶屏时钟,STC12C5A60S2的10位ADC功能程序

/* 程序名:    LCD1602,4位数据总线液晶屏时钟,STC12C5A60S2的10位ADC功能程序 编写时间:  2015年10月4日 硬件支持:  LCD1602液晶屏 STC12C5A60S2 外部12MHZ晶振 接线定义: DB7 --> P1^7DB6…

WPF|黑暗模式的钱包支付仪表盘界面设计

收集下大家的意见,是否需要在文中贴上源码(文末会给出源码链接),请大家踊跃留言。阅读目录效果展示准备简单说明 源码结尾(视频及源码仓库)1. 效果展示欣赏效果:2. 准备创建一个WPF工程&#x…

《看聊天记录都学不会C语言?太菜了吧》(3)人艰不拆,代码都在谈恋爱?!

若是大一学子或者是真心想学习刚入门的小伙伴可以私聊我,若你是真心学习可以送你书籍,指导你学习,给予你目标方向的学习路线,无套路,博客为证。 本系列文章将会以通俗易懂的对话方式进行教学,对话中将涵盖…

DB2常用命令

查看DB2License信息 DB2基础命令 转载于:https://www.cnblogs.com/arcer/p/5573317.html

.NET7 Preview4之MapGroup

这篇是“闻(看)香(码)识(学)女(技)人(术)”。这也是一个有意思的功能,路由分组,啥也不说了,看代码看结果:using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.OpenApi;var builder WebApplication.Create…

【空间数据库】ArcGIS 10.6 Database_Server_Desktop安装、连接数据库服务、创建企业级数据库(附server10.6.ecp)

由于作者一直使用SQL Server 2008 R2开发版,之前在ArcGIS中创建企业级数据库都是基于单独安装的SQL Server 2008 R2开发版,今天我们演示安装ArcGIS10.6自带的数据库服务(SQL Server 2014 Express版本)、连接数据库服务和创建数据库。 首先,我们来看一下完整的ArcGIS10.6安…

(一)easyUI之树形网络

树形网格&#xff08;TreeGrid&#xff09;可以展示有限空间上带有多列和复杂数据电子表 一、案例一&#xff1a;按tree的数据结构来生成 前台<% page language"java" contentType"text/html; charsetUTF-8"pageEncoding"UTF-8"%> <!DO…

《看聊天记录都学不会C语言?太菜了吧》(4)零基础的我原来早就学会编程了?

若是大一学子或者是真心想学习刚入门的小伙伴可以私聊我&#xff0c;若你是真心学习可以送你书籍&#xff0c;指导你学习&#xff0c;给予你目标方向的学习路线&#xff0c;无套路&#xff0c;博客为证。 本系列文章将会以通俗易懂的对话方式进行教学&#xff0c;对话中将涵盖…

二叉树结构 codevs 1029 遍历问题

codevs 1029 遍历问题 时间限制: 1 s空间限制: 128000 KB题目等级 : 钻石 Diamond题目描述 Description我们都很熟悉二叉树的前序、中序、后序遍历&#xff0c;在数据结构中常提出这样的问题&#xff1a;已知一棵二叉树的前序和中序遍历&#xff0c;求它的后序遍历&#xff0c;…

java的概率的程序_java实现一个抽奖概率类

在一些项目需求中&#xff0c;可能会遇到抽奖问题&#xff0c;如提供一系列奖品及获奖概率&#xff0c;要求根据概率返回每次抽到的奖品。以下是本人在实际项目中写的一个抽奖工具类&#xff0c;与大家共同分享&#xff1a;import java.util.ArrayList;import java.util.List;i…

【空间数据库】ArcGIS10.6连接PostgreSQL数据库并显示数据至ArcMap中

前面的文章《【开源数据库】Windows操作系统PostgreSQL+PostGIS环境搭建图文安装教程 》讲解了在Windows上安装开源GIS和开源数据库。本文接着来讲采用ArcGIS 10.6连接PostgreSQL数据库,并加载矢量数据到ArcMap中。 我们已经在pgAdmin中创建了一个空间数据库test,并导入了Sha…

算法-低位优先的字符串排序

低位优先的字符串排序相当于是对键索引计数方法的一个扩展&#xff0c;主要用于处理固定长度字符串&#xff0c;比如说手机号&#xff0c;固定电话&#xff0c;银行卡卡号&#xff0c;字符串的长度为N&#xff0c;从右向左开始进行每个键作为值开始遍历&#xff0c;实现比较简单…

使用 AgileConfig 动态配置 NLog

NLog 是我们在 .NET 领域使用非常广泛的日志组件。它默认使用 xml 来维护它的配置。最近有几个同学问我当使用 AgileConfig 的时候如何配置 NLog 。因为 AgileConfig 不支持集成 xml 格式的配置。其实 NLog 是支持从 appsettings.json / IConfiguration 读取配置的&#xff0c;…