精华内容
下载资源
问答
  • 代码覆盖率

    千次阅读 2018-11-28 17:15:35
    代码覆盖率指我们运行所有项目中的测试方法后,能够执行的语句和所有语句的比值。更具体一些,我们用行覆盖率举例,行覆盖率应该等于运行所有测试方法能够执行到的行数除以代码所有能够执行的行数。 客观看待代码...

    代码覆盖率指我们运行所有项目中的测试方法后,能够执行的语句和所有语句的比值。更具体一些,我们用行覆盖率举例,行覆盖率应该等于运行所有测试方法能够执行到的行数除以代码所有能够执行的行数。

    客观看待代码覆盖率

    简单的说,覆盖率是指我们代码在测试中能够被覆盖的程度。所以,覆盖率理论上来说越高越好,代码覆盖率高说明我们的每一段代码都经过了测试,得到了预期的答案。但我们也不应该太纠结于代码覆盖率的高低,客观地说:

    • 并不是越高的代码覆盖率表示代码质量越好BUG越少

    • 代码覆盖率高只能表示代码都被测试过,但是否可靠并不确定

    • 虽然高的覆盖率并不一定是好代码,但覆盖率低很大程度上代码质量会有问题

    • 没有覆盖的代码应该引起我们的重视,有存在问题的风险

    覆盖率的分类

    刚才我们一直笼统的说代码覆盖率,实际上,根据具体情况的不同代码覆盖率有很多不同的分类。

    在介绍各类覆盖率之前我们再明确一下概念,覆盖率是我们自己编写的测试方法对自己的代码覆盖的程度,本身的操作者就是开发者自己,而不是其他的CI工具帮我们进行测试,CI工具能做的就是运行我们的测试方法,计算出覆盖率。我们根据他们得到的覆盖率继续查缺补漏,覆盖代码。

    • 行覆盖率

      • 也叫做:语句覆盖、段覆盖、基本块覆盖

      • 行覆盖率是最基本的指标,表示是否代码中的每个可执行语句都被执行过

      • 在有些情况下不可能做到行覆盖率100%,90%的覆盖率已经很优秀,一般我们要求80%的行覆盖率

    • 分支覆盖率

      • 也叫做:条件覆盖、判断覆盖、边界覆盖

      • 分支覆盖使用一组测试参数来测试是否代码中所有的分支都能被测试到了

      • 我们的测试方法要对分支进行不同参数的测试,一般情况下我们要求60%的分支覆盖率

    • 路径覆盖率

      • 也叫做:断言覆盖

      • 之前的分支覆盖的问题在于,可能在不同的参数下能够达到不同的分支,但是不通的分支达到情况是否会带来问题这是分支覆盖不能测试出来的,如果有一种测试方法,对包括所有分支在内的所有的路径都能测试一遍就完美了,这就是路径覆盖

      • 一般来说路径覆盖对于开发来说负担太大,一旦分支增多路径覆盖所需的测试样例指数增长,所以我们一般不会用路径覆盖做覆盖率的统计

    • 变更行覆盖率

      • 上一次发布代码后更新的代码的行覆盖率,这个数据可以方便的看出你新的代码是否做了测试

    如何计算覆盖率

    上图是一个方法执行的内部情况,这个方法开始时先执行一些代码,把这些执行的代码称作A,之后有一个if-else判断条件BC,如果是if就走B、else就走C,DE也是一样,最后这个方法还会执行一些代码,我们把这些代码当作F。

    • 行覆盖率

      • 统计行覆盖率的时候不关心是否有条件判断,我们只会跑完所有的测试方法,最后执行了几行除以总行数,得到一个行覆盖率

    • 分支覆盖率

      • 如果我们只是在测试样例中调用了一次这个方法,那么假如这一次方法的调用执行了B下一个条件执行了D,这样我们的分支覆盖率是50%。如果我们想提高分支覆盖了,就要在测试样例中多次调用方法传入不同参数,在多次调用中让代码走其他的分支,比如我们多调用了一次,这次代码走了C和D,这样我们调用两次,一次BD、一次CD,这样分支覆盖率就是75%。如果我们用多次调用把BCDE全部测试到了,那么分支覆盖率就是100%

    • 路径覆盖率

      • 路径覆盖率说起来很麻烦,其实很好理解。我们像这样一个问题,假如现在你在旅游,A到F是6个景点,你一共有多少种走法?所有的走法总和就是路径覆盖率的分母。这个方法分支比较少,我们可以穷举,一共有下面这些走法:ABDF、ABEF、ACDF、ACEF。如果我们在测试样例中走通上面四种中的一种,那么我们的路径覆盖率就是25%,以此类推。可想而知在分支很多的情况下,想做到很高的路径覆盖率是几乎不可能的,所以我们一般不会统计这种覆盖率。

    代码覆盖率的工具

    我们已经了解了什么是代码覆盖率,作为开发怎么样让自己写出高覆盖率的测试。那么现在我们在CI集成人员的角度来看,代码覆盖率是如何统计的,有哪些代码覆盖率的工具。现在比较常用的代码覆盖率工具有Jacoco和Cobertura。Emma也是一个不错的覆盖率统计工具,但是他不支持JDK8,我们暂且先不讨论。

    这些代码覆盖率工具的工作流程大概是这样的:

    • 在class文件在JVM中的某个瞬间进行插桩

    • 执行测试用例的时候,将执行的轨迹Dump到内存中存放

    • 数据处理器结合插桩的数据分析代码覆盖率

    为什么我们说是在JVM中class文件某个瞬间进行插桩呢?是因为普遍我们有三类插桩模式:

    • On-The-Fly代理模式

      • 通过设置JVM参数指定特定的jar执行代理程序,将探针插入class文件

      • Jacoco使用这种方式

    • On-The-Fly类加载器模式

      • 通过自定义类加载器实现类类装载的时候插入探针

      • Emma使用这种方式

    • Offine模式

      • 在class文件要加载到JXM之前直接进行插桩插入探针,修改文件

      • Cobertura使用这种方式

    On-The-Fly和Offine比较:

    • On-The-Fly模式更加方便的获取代码覆盖率,无需提前进行字节码插桩,可以实时获取代码覆盖率信息

    • Offline模式适用于以下场景:

      • 运行环境不支持java agent

      • 部署环境不允许设置JVM参数

      • 字节码需要被转换成其他虚拟机字节码,如Android Dalvik VM

      • 动态修改字节码过程中和其他agent冲突

      • 无法自定义用户加载类

    展开全文
  • 代码覆盖率原理简介

    千次阅读 2021-02-08 16:42:26
    随着项目迭代的不断深入,工程逻辑与用户场景日益复杂,传统的白盒测试体系已经无法适应苛刻的工程质量要求,质量评估也不再单纯的依赖bug率和性能指标,而需要精准的数据来量化代码质量,代码覆盖率就是其中的一项...

    随着项目迭代的不断深入,工程逻辑与用户场景日益复杂,传统的白盒测试体系已经无法适应苛刻的工程质量要求,质量评估也不再单纯的依赖bug率和性能指标,而需要精准的数据来量化代码质量,代码覆盖率就是其中的一项重要标准。

     

     

    代码覆盖率简述

     

    什么是代码覆盖率

     

    代码覆盖率测试技术是一种常见的白盒测试技术,是衡量软件测试工作充分性和完整性的重要指标之一。

     

    简单来说,代码覆盖率就是测试过程中已经被执行过的代码占准备测试总代码量的比例和程度,它关注的是在执行用例时,有哪些代码被执行到了,有哪些代码没有被执行到。 

     

    代码覆盖率的价值

     

    代码覆盖率的分析在一定程度上能够评判代码质量,一般覆盖率高的代码出错的几率会相对低一些。但是高覆盖率的代码只能表示执行了很多的代码,并意味着这些代码被很好的执行了。

    那么,代码覆盖率到底能给我们带来怎样的价值呢?

     

    首先,对于测试来说,代码覆盖率最主要的意义是帮助我们了解测试情况,可以通过分析未覆盖部分的代码,从而反推在前期测试设计是否充分,没有覆盖到的代码是否是测试设计的盲点,之前为什么没有考虑到?或许是需求/设计不够清晰,测试设计的理解有误,工程方法应用后的造成的策略性放弃等等,之后进行补充测试用例设计。

     

    其次,其有助于发现多个测试用例都覆盖不到的代码,可以逆向反推在代码设计中思维混乱点,提醒设计/开发人员理清代码逻辑,提升代码质量;同时为废弃代码提供依据。

     

    此外,代码覆盖率可以度量单元/自动化测试用例,提供覆盖率统计情况,可以通过分析覆盖率报告,完善用例。

     

    最后,代码覆盖率利于精准回归,通过构建代码调用关系,精准的确定回归测试范围,避免了全量回归造成的测试资源浪费。

     

    常用指标

     

    • 语句覆盖

      又称行覆盖(LineCoverage),指已经被执行到的语句占总可执行语句(不包含类似C++的头文件声明、代码注释、空行等等)的百分比,这是最常用的也是要求最低的覆盖率指标,实际中通常会结合判定覆盖率或者条件覆盖率一起使用。

    • 判定覆盖

      又称分支覆盖(BranchCoverage),用以度量程序中每一个判定的分支是否都被测试到了,即代码中每个判定的"真"和"假"至少执行一次。

      这句话是需要进一步理解的,应该非常容易和下面说到的条件覆盖混淆。因此我们直接介绍第三种覆盖方式,做个对比,就明白两者是怎么回事了。

    • 条件覆盖

      度量判定中的每个子表达式结果true和false是否都被测试到了

    • 路径覆盖率、函数覆盖率、类覆盖率、指令覆盖率等指标

     

    为了说明判定覆盖和条件覆盖的区别,我们来举一个例子,假如我们的被测代码如下:

    int foo(int a, int b){ if (a < 10 || b < 10) // 判定 { return 0; // 分支一 } else { return 1; // 分支二 }}

    在设计判定覆盖的测试用例时,我们只需要考虑到判定结果为true和false两种情况,因此我们只需要设计如下的case,就能达到判定覆盖率100%:

    a = 5, b = 任意数字  覆盖了分支一

    a = 15, b = 15   覆盖了分支二

     

    设计条件覆盖案例时,我们需要考虑到判定中每个表达式的结果,为了达到覆盖率100%,设计了如下案例:

    a=5    (条件a<10的值为“真”)

    a=15   (条件a<10的值为“假”)

    b=5    (条件b<10的值为“真”)

    b=15   (条件b<10的值为“假”)

     

    通过上面的例子,应该很清楚的了解了判定覆盖和条件覆盖的区别。

    需要注意的是:条件覆盖不是将判定中的个条件表达式的结果进行排列组合,而是只要每个条件表达式的结果true和false测试到就可以了。

    同时,我们可以这样推论:完全的条件覆盖并不能保证完全的判定覆盖。  

     

    JAVA覆盖率工具介绍

     

    目前Java常用的覆盖率工具有JaCoCo、Emma和Cobertura、Clover(商用),详细介绍请看下表:

    其中Emma和Cobertura已经停止维护了,只有JaCoCo还在不断的更新,JaCoCo社区也比较活跃,所以现在使用的最为广泛的就是JaCoCo了。

    目前我司选用的也是JaCoCo。下面让我们一起来看下JaCoCo的原理、使用和在公司的实践吧。

     

    关于JaCoCo

     

    JaCoCo简述

     

    JaCoCo是一个开源的覆盖率工具

    (官方文档地址:

    https://www.jacoco.org/jacoco/trunk/doc/index.html),

    它针对的开发语言是java,其使用方法很灵活,可以嵌入到Ant、Maven中;可以作为Eclipse插件;也可以使用JavaAgent技术监控Java程序等等。

    很多第三方的工具提供了对JaCoCo的集成,如sonar、Jenkins等。

     

    JaCoCo原理简介

     

    JaCoCo到底是怎么手机覆盖率信息的呢?

    插桩

     

    何谓插桩?

    用通俗的话来讲,插桩是将一段代码通过某种策略插入到另一段代码,或者替换另一段代码,来收集程序运行时的动态上下文信息。

     

    这里的代码既可以是字节码也可以是源码。

     

    JaCoCo就是字节码插桩方式

     

    其中字节码插桩又分为on-the-flyoffline的两种模式。

     

    on-the-fly模式

    在JVM中通过添加-java agent参数指定特定的jar文件启动Instrumentation的代理程序,代理程序会在ClassLoader装载一个class前判断是否已经转换修改了该文件,如果没有则将探针插入class文件中,探针不改变原有方法的行为,只是记录是否已经执行。

     

    offline模式

    在测试之前先对文件进行插桩,生成插过桩的class或jar包,测试插过桩的class和jar包,生成覆盖率信息到文件,最后再统一处理,生成报告。

     

    on-the-fly和offline对比

    on-the-fly更方便简单,无需提前插桩,无需考虑classpath设置问题。

     

    以下情况不适合使用on-the-fly模式:

    1. 不支持javaagent

    2. 无法设置JVM参数

    3. 字节码需要被转换成其他虚拟机

    4. 动态修改字节码过程和其他agent冲突

    5. 无法自定义用户加载类

     

    如下图,包含了几种不同的覆盖率信息的收集方法,其中带颜色的是JaCoCo比较有特色的部分:

    图片来源:官网*

     

    关于JaCoCo具体的注入原理,在官方网站上写的非常详细了,网上翻译修改的资料也非常多,这里不做过多赘述。

     

    经过对比,我们在统计功能测试覆盖率以及集成测试覆盖率时,选择的是On-the-fly模式。

    原因是On-the-fly方式无须入侵应用启动脚本,只需在 JVM 中通过 -javaagent 参数指定JaCoCo的代理程序。

     

    Jacoco使用方式

     

    Apache Ant方式

     

    参考:JaCoCo Ant方式使用

    https://www.eclemma.org/jacoco/trunk/doc/ant.html

     

    Apache Maven方式

     

    参考:JaCoCo Maven方式使用

    这种方式适合maven项目。

    https://www.eclemma.org/jacoco/trunk/doc/maven.html

     

    Eclipse EclDmma Plugin方式

     

    参考:JaCoCo Eclipse使用

    这种方式主要和eclipse集成,用户可以直观的看到覆盖率的情况。

    https://www.eclemma.org/

     

    命令行方式

     

    官方文档上详细介绍了用到的参数和用法,其主要使用如下JVM参数来激活Java agent代理:

    -javaagent:[yourpath/]jacocoagent.jar=[option1]=[value1],[option2]=[value2]

    但是它也接受一些其他的参数,详情可查看官方文档。

    目前我司是使用此方式来统计代码覆盖率的。下面一起来看下具体是如何使用的吧!

     

    更改server 的启动脚本,

    使用jacocoagent.jar启动服务

    1. 在官网下载JaCoCo的jar包;

    2. 修改启动server的参数,使用jacocoagent.jar记录服务的操作数据,启动项增加下面内容。

      (更多参数请看官网)-

      javaagent:/tmp/testjacoco/lib/jacocoagent.jar=includes=com.*.*,output=tcpserver,port=39512,address=服务器ip,append=true

      系统在jvm停止的时候会dump覆盖率信息,存储在exec文件中。

     

    生成覆盖率报告

    1. 从服务器上获取生成的exec文件放到部署覆盖率平台服务的机器上;

    2. 调用JaCoCo的api生成报告,exec地址为启动脚本中destfile指定的文件。

      理论上不用杀server进程就可以直接copy到最新的exec文件,但是如果遇到报告结果是空的情况,可以考虑先kill server进程,再拷贝exec文件。

      参考官方demo的有具体的示例:ReportGenerator.java

      https://www.eclemma.org/jacoco/trunk/doc/examples/java/ReportGenerator.java

      部分代码如下:

    public testjacoco(final File projectDirectory ) {    this.title = projectDirectory.getName();    this.executionDataFile = new File(projectDirectory, "scfzzpostjacoco.exec");    //目录下必须包含源码编译过的class文件,用来统计覆盖率。所以这里用server打出的jar包地址即可    this.classesDirectory = new File(projectDirectory, "/");    // this.sourceDirectory =null;    //源码的/src/main/java,只有写了源码地址覆盖率报告才能打开到代码层。使用jar只有数据结果    this.sourceDirectory = new File("/opt/RD_Code/server/zhuanzhuan_scf_zzpost_4-0-38_BRANCH/service/", "src/main/java");    //coveragereport为要保存报告的地址    this.reportDirectory = new File(projectDirectory, "coveragereport");}

    这里主要是调用testjacoco()方法来做入口生成报告。

    其中,

    this.title是报告的标题;

    this.executionDataFile是第一步生成的exec的文件;

    this.classesDirectory是源码的class文件,只要传递class所在目录就可以(或者用编译过的jar包也可以),不传递会报错,用来统计覆盖率 ;

    this.sourceDirectory是源码所在目录,可以不赋值使用null,但这种覆盖率结果只有看到方法名级别,不能直接看到方法中具体的覆盖结果 。

     

    这一步完成之后,我们就可以在通过在浏览器查看html报告,来具体的分析代码覆盖率。

     

    得物app实践

     

    目前我们基于公司的业务模式,对JaCoCo做定制化改造,搭建了覆盖率平台,支持产出增量覆盖率和全量覆盖率报告,也支持手动更新全量覆盖率数据。

     

    由上文我们可以了解到,我们使用JaCoCo最终目的是需要生成覆盖率报告,可以拆分为以下几个步骤:

    1. 测试完成之后生成的exec统计文件;

    2. 插桩后的classes文件;

    3. 本次部署的服务在gitlab上的代码;

    4. 利用JaCoCo的api生成报告。

       

    目前覆盖率平台一期只支持全量覆盖率,是通过定时执行接口自动化任务来获取数据的。

     

    那具体实现流程是怎样的呢?实践过程中遇到了哪些问题,又是如何解决的呢?

    一起来了解一下吧!

     

    获取生成的exec统计文件

     

    首先我们要修改服务的启动脚本,带上JaCoCo用以插桩并监听启动参数。

    •  
    javaagent:/home/ops/testjacoco/lib/jacocoagent.jar=includes=com.sz.*,output=tcpserver,port=33511,address=10.11.22.19,append=true

    其中,

    /home/ops/testjacoco/lib/jacocoagent.jar是JaCoCo jar包的目录;

    includes=com.sz.* 是对包进行过滤;

    output=tcpserver表示以tcpserver方式启动应用并进行插桩;

    port=33511是jacoco开启的tcpserver的端口,请注意这个端口不能被占用;

    address=*.*.*.* 是对外开放的的tcpserver的访问地址,可以配置127.0.0.1,也可以配置为实际访问ip:

    配置为127.0.0.1的时候,dump数据只能在这台服务器上进行dump,就不能通过远程方式dump数据;

    配置为实际的ip地址的时候,就可以在任意一台机器上(前提是ip要通,不通都白瞎),通过ant xml或者api方式dump数据。

     

     

    在实践的时候发现脚本里不能写死监听端口,因为服务器上启动了多个应用服务,且端口都是随机的,一旦端口被占用会导致应用启动失败。

     

    为了解决这个问题,我们在启动服务的时候先执行shell脚本获取未占用的端口,给JaCoCo使用。

    其次,覆盖率平台服务需要获取每个服务的端口信息。JaCoCo自身是通过Jsch在应用服务器上执行命令,获取被统计应用服务所使用的JaCoCo端口。但是Jsch在只能执行单条指令,对shell有限制,所以我们自己编写了一个脚本,将服务信息和端口信息先输出到文件中(如下图),再将文件传输到覆盖率平台所在服务器。

    图片

    使用JaCoCo启动了服务器,拿到了端口号,第三步就是需要执行接口自动化,获取exec统计文件,我们通过启动定时任务,定时执行脚本,获取覆盖率数据。

     

    为了获取纯净的覆盖率数据,执行脚本的时间,既不能影响测试流程又需要在自动化case运行完成的时候统计,单纯使用定时任务并不能很好的满足目前需求。因为接口自动化case运行时长不定、服务部署时长也不固定。在这里我们通过记录两个运行状态,来决定统计的时间。

     

    在使用scheduled任务的时候遇到了一个问题,在定时任务类中使用@Autowired注解时,会报空指针异常,因为mapper实例化时为null,不能调用mapper实现中的方法,因为Spring的Schedule是通过Quartz实现的,但默认时, 并不直接支持ApplicationContext 。在实际项目中,我们用一个类实现了ApplicationContextAware接口。这样,这个类可以直接获取 Spring 配置文件中,所有有引用到的Bean对象。

    实现代码如下:

     

    @Componentpublic class WorkUtil implements ApplicationContextAware {    private static ApplicationContext context;    @Override    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {        context = applicationContext;    }    public static ApplicationContext getApplicationContext() {        return context;    }    public static Object getBeans(String name) {        return getApplicationContext().getBean(name);    }}

    我们在使用时,直接调用getBeans()方法即可获取bean对象。

     

    获取classes文件

     

    得物服务的代码,为多moudle模式,打jar包时,一些实现业务逻辑moudle被打成jar包跟一些第三方依赖包混淆在一起。而我们统计覆盖率的时候,只需要统计放真正代码逻辑的jar包,如果包含一些第三方依赖包含的东西太多会显得很杂乱。

    通过修改JaCoCo源码,在字节码处理的时做判断,将公司的业务代码从整个project中拆出来,然后在生成报告这步中进行聚合,生成一个较为简洁的报告。

     

    在最后做统计的时候,JaCoCo需要插桩,JaCoCo会在编译期间加入JacocoData成员变量,如果使用循环反射成员变量,没有做校验的情况中,代码中会多出$jacocoData变量,这样会影响到被统计服务运行,我们在代码中加如下的if判断,来检查成员变量是否有java编译器引入的,如果不是则跳过,继续执行原有逻辑。

     

    if (!(Object instanceof ColomboComponent)){        continue;    }

     

    获取git上的源码

     

    这一步主要是通过JGit实现的,在git pull代码时会出现Auth fail的错误,经排查发现是连接超时或者文件过大导致的。最后在服务器上修改git配置,解决了这个问题。

    具体设置如下:

    ​​​​​​​

    git config --global http.lowSpeedLimit 0git config --global http.lowSpeedTime 999999git config http.postBuffer 524288000

     

    生成覆盖率报告

     

    前面的准备工作都做好了,再生成覆盖率报告的时候发现,由于版权保护问题,修改了JaCoCo源码会导致服务编译不通过。梳理了代码逻辑,确认代码各个方法都没问题,最后在代码中添加了如下注释,编译成功!

    具体注释如下:

    /** * Encapsulates the tasks to create reports for Maven projects. Instances are * supposed to be used in the following sequence: * * <ol> * <li>Create an instance</li> * <li>Load one or multiple exec files with * <code>loadExecutionData()</code></li> * <li>Add one or multiple formatters with <code>addXXX()</code> methods</li> * <li>Create the root visitor with <code>initRootVisitor()</code></li> * <li>Process one or multiple projects with <code>processProject()</code></li> * </ol> */

     

    在平台化的过程中,大部分的时间都放在了修改JaCoCo源码上。

     

    因为JaCoCo源码中,用到的全局变量比较多,如果不从头开始读,无法发现这个变量是什么时候被赋值的,代码中也会包含以下类似于s1、s2这中变量名,读起来比较费劲,只能通读整个代码,同时也有助于我们更深入的了解了JaCoCo的实现。

     

    我们也在不断的对平台的功能进行完善。除了二期实现增量覆盖率功能以外,后续也会将覆盖率的统计功能做的完善一些。在积累数个版本的全量覆盖率之后,可以建立预警机制,输出每日开发自测、测试人员手动测试、自动测试覆盖率,分析合理的增长趋势。如果偏离该趋势,则及时进行预警。也可以和用例平台打通,获取到每条用例覆盖到的函数和影响到的接口,通过更小的维度更精准地度量测试质量。

     

    总结

     

    通过引入代码覆盖率分析体系,我们可以精确把控增量代码质量,持续改善优化存量代码。同时也可以将测试用例的影响范围细化到代码层面,从而实现精准化测试。

     

    但是,盲目的追求代码覆盖率是没有意义的,即使已经达到了100%的代码覆盖率,软件的质量也不可能做到万无一失,因为代码覆盖率的计算是基于现有代码的,并不能发现那些「未考虑某些输入」以及「未处理某些情况」形成的缺陷

     

    并且在追求更高的代码覆盖率时,我们需要补充更多的case,去覆盖更多的代码,这样测试成本也会以指数级的方式迅速增加,花费的时间成本会也会更高。

     

    所以,我们在实际工作中,需要正确恰当地应用代码覆盖率,使其能够帮助我们更精准地定位和分析问题,保证产品质量,为精准测试添砖加瓦,发挥它的最大价值。

     

    公众号原文:https://mp.weixin.qq.com/s/FMWfE76WyLvJVidIjNCLcg

    展开全文
  • 代码覆盖率是衡量软件测试完成情况的指标,通常基于测试过程中已检查的程序源代码比例 计算得出。代码覆盖率可以有效避免包含未测试代码的程序被发布。 代码覆盖率能不能提高软件的可靠性?答案是肯定的,代码的...

    简介
    代码覆盖率是衡量软件测试完成情况的指标,通常基于测试过程中已检查的程序源代码比例 计算得出。代码覆盖率可以有效避免包含未测试代码的程序被发布。
    代码覆盖率能不能提高软件的可靠性?答案是肯定的,代码的覆盖率分析是保证软件质量最简 便易行的方法。

    代码覆盖率等级
    代码覆盖率可以通过多种方法测量。最常用的是测量以下一个或多个指标:语句覆盖率,分支 覆盖率,修订的条件/判定覆盖率(MC/DC)。以下章节中将逐一详解这些代码覆盖率。
     
    语句覆盖率
    语句覆盖率用来度量被测代码中的可执行语句是否被执行到,它并不考虑循环或者条件语句, 只针对语句度量可执行代码。应当特别注意的是:“语句”并不等同于代码行。
    一般情况下,对于  C,C++,Java或Ada,分号代表语句结束。在某些情况下,一条语句会跨越多 行代码。语句覆盖率可以有效度量可执行代码是否被执行,但同时也有一定的局限性。

    语句覆盖率的局限
    考虑如下图1的代码段: 

    int* p = NULL;
    if (condition)
    p = &variable;
    *p = 123;
    图  1 –  语句覆盖局限代码示例

    如果“condition”为true,那么就有可能达到100%的语句覆盖,然而这个测试用例忽略了另 一种情况:如果“condition”为假,程序将引用空指针,因此,虽然语句覆盖率是一个很好 的度量指标,它仍旧是入门级的代码覆盖率。理想情况下,即使“condition”为false,测试 用例也应当被计算。

    分支覆盖率

    分支覆盖率用来度量程序中所有的判定和分支以及相应的输出是否都被测试执行到,例如 “if”语句必须将“true”和“false”都考虑到以覆盖所有的输出。如果只有一个路径被执行,那么覆盖率将被标记为部分执行。
     
    和语句覆盖率类似,分支覆盖浪费也有一些需要注意的细节,尤其在针对“惰性求值”的编程语言时,惰性求值是将代码的求值操作延迟到需要结果值时再进行的一项技术。

    分支覆盖率的局限
    典型的情况是当有复杂的布尔表达式的“惰性求值”出现时,如下图2的代码片段:
     
    int* p = NULL;
    if (condition1 && (condition2 || function1(*p)))
    statement1;
    else
    图  2 –  分支覆盖率局限代码示例

    考虑“condition1”为假的情况,惰性求值将不会度量“condition2”或,此种情况同样会导致 代码“if (condition1 && (condition2 || function1(*p)))”的分支覆盖率计算错误。
     
    继续考虑“condition1”和“condition2”都为真的情况。惰性求值将再次导致“function1(*p)” 不会被度量,也同样会导致代码“if (condition1 && (condition2 || function1(*p)))”的分支覆盖率 计算错误。在此种情况下,有可能出现分支覆盖率为100%但软件中仍有潜在缺陷的情况。

    修订条件/判定覆盖率(MC/DC)
    MC/DC是一种特殊的分支覆盖率,它不但会使用分支覆盖率报告复杂条件下的true和false输出,同时也会报告复杂条件下的全部分支条件输出。
     
    MC/DC最初由波音公司创建,用于航空软件中DO-178B的A级认证。通过对所有的子条件输出分支的独立证明,有效解决了惰性求值带来的问题。
     
    继续讨论代码示例2,我们需要在“condition2”和“function1(*p)”固定的条件下验证“condition1” 的“true”和“false”判定分支,之后继续固定“condition1”和“function1(*p)”验证“condition2” 的判定分支。
     
    同样的,让我们在固定“condition1”和“condition2”的条件下讨论  “function1(*p)”。在其他分支条件固定的情况下验证某个分支条件的“true”和“false”值称作“MC/DC对”。MC/DC对一般使用MC/DC真值表描述。表1就是一个MC/DC真值表示例。



     
    在软件开发的不同阶段获取覆盖率
    软件测试有很多种类,本文将其简要的分为三类:
    > 系统/函数测试:  测试集成后的整个应用
    > 集成测试::测试集成的子系统
    > 单元测试:测试一个或多个文件或类
     
    每个软件项目在系统测试的过程中都会模拟最终用户的操作对源代码做一些系统测试。导致软件发布后仍旧存在缺陷最重要的一个原因通常是程序在运行过程中遇到了非预期的,即没有测试的输入组合。
     
    很多软件项目并不是没有做集成测试或者单元测试。只是在完成集成测试或单元测试后,开发团队可能苦于为隔离程序中的单个或多个文必须所需的大量测试代码量。
     
    对于最严格的单元测试和集成测试来说,最终生成的测试代码量比待测代码量还要庞大是很经常出现的情况。因此,这两种级别的测试普遍适用于关键和高安全领域,例如:航空航天、医疗、交通运输、工业过程控制、高速汽车等。此类软件中包含大量的嵌入式应用软件。
     
    关键领域的结构化测试流程一般会将需求的级别高低作为重点,代码覆盖率因而会在这种“基于需求”的测试中进行分析。在许多项目中,高等级的需求最先被测试。此时代码覆盖率可以被用来检测和报告所达到的覆盖比例。
     
    然而不幸的是,在系统测试和功能测试阶段想要达到100%的代码覆盖率几乎是不可能的。通常情况下系统测试和功能测试只能达到60%-70%的代码覆盖率,剩余30%-40%的代码覆盖率需要在单元测试和集成测试阶段才能够完成。
     
    单元测试使用包含驱动和桩的测试代码隔离系统中的特定函数,同时使用测试用例模拟这些函数的执行。这些所谓的“低等级测试需求”  对被测试代码提供了更高的控制,可以提高先前执行的系统测试覆盖率(甚至能达到100%)。因此,在不同的测试之间共享覆盖率数据是非常有必要的。

     嵌入式环境中获取覆盖率带来的挑战
    常言道“有得必有失”,在嵌入式环境获取代码覆盖率的问题上,要付出的代价是对待测代码额外的插桩工作。
    插桩是将额外的代码添加到程序中,从而实现测试过程中的覆盖率收集和分析操作。
    由于插桩的相关操作将导致程序源代码增多,进而延长程序的执行时间,因而需要预测插桩后的源代码的覆盖范围预测,尤其当测试实时嵌入式系统环境时,此项工作就更为重要。
    事实上,要精准的预测程序文件插桩的影响几乎是不可能的。没有算法支持(也不可能有)。 每个系统都包含很多的变量,具有独立唯一的复杂性。当然,对于典型的示例系统来说,获取一组准确的估计还是可能实现的。

    在共享环境中获取覆盖率数据
    在嵌入式环境下管理代码覆盖率的主要问题在于如何配置内存以容纳额外的插桩代码。
    VectorCAST针对大量示例代码评估后发现添加了上文中提出的各种覆盖率额外配置之后,源代码量增长量普遍达到了10%。对于绝大多数的32位目标板,这并不是一个很大的问题,但对于存储容量有限的8位或者16位目标板来说,几乎可以肯定这会是一个问题。 

    为了降低可执行文件的大小,各种各样的代码插桩技术被发明出来,针对不同大小的存储区域有不同的数据采集技术。植入存储器内部的收集系统可以用于监测被检测到的代码。这是插桩技术中保证使用最少RAM的关键技术。


    展开全文
  • gcov代码覆盖率测试-原理和实践总结

    千次阅读 2019-05-28 19:07:59
    目录 一、gcov简介 ...四、gcov检测代码覆盖率的原理 五、服务程序覆盖率统计 六、内核和模块的gcov代码覆盖率测试 七、lcov工具使用 八、info文件格式信息 九、例子 FAQ 参考 感谢 一、gcov简介 gco...

    阅读:https://github.com/yanxiangyfg/gcov
    gcov源码:http://www.opensource.apple.com/source/gcc/gcc-5484/gcc/gcov.c

    一、gcov简介

    gcov是什么

    • gcov是一个测试代码覆盖率的工具。与GCC一起使用来分析程序,以帮助创建更高效、更快的运行代码,并发现程序的未测试部分
    • 是一个命令行方式的控制台程序。需要结合lcov,gcovr等前端图形工具才能实现统计数据图形化
    • 伴随GCC发布,不需要单独下载gcov工具。配合GCC共同实现对c/c++文件的语句覆盖和分支覆盖测试
    • 与程序概要分析工具(profiling tool,例如gprof)一起工作,可以估计程序中哪段代码最耗时

    gcov能做什么

    使用象gcov或gprof这样的分析器,您可以找到一些基本的性能统计数据:
    * 每一行代码执行的频率是多少
    * 实际执行了哪些行代码,配合测试用例达到满意的覆盖率和预期工作
    * 每段代码使用了多少计算时间,从而找到热点优化代码
    * gcov创建一个sourcefile.gcov的日志文件,此文件标识源文件sourcefile.c每一行执行的次数,您可以与gprof一起使用这些日志文件来帮助优化程序的性能。gprof提供了您可以使用的时间信息以及从gcov获得的信息。

    注意事项

    • 通过将一些代码行合并到一个函数中,可能不会提供足够的信息来查找代码使用大量计算机时间的“热点”。同样地,由于gcov按行(在最低的分辨率下)积累统计数据,它最适合于只在每行上放置一个语句的编程风格。如果您使用扩展到循环或其他控制结构的复杂宏,那么统计信息就没有那么有用了——它们只报告出现宏调用的行。如果您的复杂宏的行为类似于函数,那么您可以用inline fu替换它们。
    • gcov只在使用GCC编译的代码上工作。它与任何其他概要或测试覆盖机制不兼容。

    二、gcov过程概况

    <主要工作流>
    • 1) 编译前,在编译器中加入编译器参数-fprofile-arcs -ftest-coverage
    • 2) 源码经过编译预处理,然后编译成汇编文件,在生成汇编文件的同时完成插桩。插桩是在生成汇编文件的阶段完成的,因此插桩是汇编时候的插桩,每个桩点插入3~4条汇编语句,直接插入生成的*.s文件中,最后汇编文件汇编生成目标文件,生成可执行文件;并且生成关联BB和ARC的.gcno文件;
    • 3) 执行可执行文件,在运行过程中之前插入桩点负责收集程序的执行信息。所谓桩点,其实就是一个变量,内存中的一个格子,对应的代码执行一次,则其值增加一次;
    • 4) 生成.gcda文件,其中有BB和ARC的执行统计次数等,由此经过加工可得到覆盖率。

    三、使用gcov的3个阶段

    1. 编译阶段

    要开启gcov功能,需要在源码编译参数中加入-fprofile-arcs -ftest-coverage
    * -ftest-coverage:在编译的时候产生.gcno文件,它包含了重建基本块图和相应的块的源码的行号的信息。
    * -fprofile-arcs:在运行编译过的程序的时候,会产生.gcda文件,它包含了弧跳变的次数等信息。

    如下以helloworld_gcov.c为例子,源码如下:

    #include <stdio.h>
    #include <string.h>
    
    int main(int argc, char *argv[])
    {   
        if (argc >=2) {
            printf("=====argc>=2\n");
            return;
        }
        printf("helloworld begin\n");
    
        if (argc <2){
            printf("=====argc<2\n");
            return;
        }
        return;
    }
    
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    helloworld_gcov.c的Makefile的书写如下,在编译选项CFLAGS中加入-fprofile-arcs -ftest-coverage选项:

    #加入gcov编译选项,通过宏PRJRELEASE=gcov控制
    ifeq ("$(PRJRELEASE)","gcov")
    CFLAGS+= -fprofile-arcs -ftest-coverage
    endif
    
    CC=gcc
    
    .PHONE: all
    
    all: helloworld
    
    helloworld: *.c
    #   编译出汇编和gcno文件
        @echo ${CFLAGS}
        @${CC} ${CFLAGS} -S -o helloworld_gcov.s helloworld_gcov.c 
        @${CC} ${CFLAGS} -o helloworld_gcov helloworld_gcov.c 
    
    .PHONE: clean
    clean:
        @-rm helloworld_gcov helloworld_gcov.gcno helloworld_gcov.gcda helloworld_gcov.c.gcov helloworld_gcov.s
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 在helloworld目录下执行make命令后,产生helloworld_gcov.s,helloworld_gcov helloworld_gcov.gcno. helloworld_gcov.gcno只要源码不变,编译出来永远不改变.
    • 运行gcov helloworld_gcov.c命令产生原始的代码覆盖率数据文件helloworld_gcov.c.gcov, 由于此时没有运行./helloworld_gcov,没有helloworld_gcov.gcda统计数据,覆盖率为0

    2. gcov收集代码运行信息

    • 运行./helloworld_gcov产生helloworld_gcov.gcda文件,其中包含了代码基本块和狐跳变次数统计信息

    3. 生成gcov代码覆盖率报告

    • 再次运行gcov helloworld_gcov.c产生的helloworld_gcov.c.gcov中包含了代码覆盖率数据,其数据的来源为helloworld_gcov.gcda
    • 为了对比运行./helloworld_gcov前后的覆盖率数据文件helloworld_gcov.c.gcov信息,直接执行如下脚本,产生前后数据对比

    $ make    #编译
    $ gcov helloworld_gcov.c          #生成原始的helloworld_gcov.c.gcov文件
    $ cp helloworld_gcov.c.gcov helloworld_gcov.c.gcov-old            #备份好原始的helloworld_gcov.c.gcov文件,方便后续对比
    $ cp helloworld_gcov.gcno helloworld_gcov.gcno-old                #备份好原始的helloworld_gcov.gcno文件,方便后续对比
    $ ./helloworld_gcov                   #产生helloworld_gcov.gcda文件,记录的代码运行的统计数据
    $ gcov helloworld_gcov.c              #根据gcda文件,再次生成helloworld_gcov.c.gcov文件
    
    
    #最后显示如下,可以对比先后的gcov文件,前后汇编文件.
    
    yangfogen@ubuntu:~/work/helloworld_gcov$ ls
    helloworld_gcov    helloworld_gcov.c.gcov      helloworld_gcov.gcda  helloworld_gcov.gcno-old  helloworld_gcov.s
    helloworld_gcov.c  helloworld_gcov.c.gcov-old  helloworld_gcov.gcno  helloworld_gcov-gcov.s    Makefile
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    • 其中#####表示未运行的行
    • 每行前面的数字表示行运行的次数
    • 上述生成的.c.gcov文件可视化成都较低,需要借助lcov,genhtml工具直接生成html报告。

      • 根据.gcno .gcda文件生成图形化报告

        包括运行mysql都尽量使用sudo,否则有些文件会打不开
        先运行:(helloworld_gcov.info为生成的文件数据集)
        $ sudo lcov -c -d . -o helloworld_gcov0604.info --rc lcov_branch_coverage=1
        再运行:(helloworld_gcov.info为输入的文件数据集)生成html
        $ sudo genhtml -o 111 helloworld_gcov.info

    • 4.lcov与genhtml的使用

      https://www.jianshu.com/p/a42bbd9de1b7

      这步可有可无,即归零所有执行过的产生覆盖率信息的统计文件:
      lcov -d ./ -z
      

      4.1 lcov生成覆盖率常用命令
      生成全量覆盖率
      lcov -b <测试代码路径> -d <gcda目录位置> -c -o result.info --rc lcov_branch_coverage=1
      参数比较多,一个个说
      -b为指定原代码路径,即生成gcno数据时编译的代码路径
      -d为gcda所在目录,可以将所有的gcda放置在一个目录中
      -c代表生成覆盖率数据,后面不用给其他参数
      -o指定生成的文件名,这里指定文件为当前目录下的result.info
      –rc lcov_branch_coverage=1表示包含分支数据

      Merg多份覆盖率数据
      lcov -a phase1.info -a phase2.info -o out.info
      其中phase1.info以及phase2.info为独立了的两份覆盖率数据,他们整合为一份out.info

      4.2 生成html格式的覆盖率报告
      genhtml -o result out.info
      其中out.info为要解析的覆盖率数据文件,生成的网页会放在result目录下

      genhtml失败时:(https://stackoverflow.com/questions/30345686/c-using-gcov-lcov-in-a-cmake-project/54900625#54900625)
      try to put --ignore-errors with genhtml command like that :
      genhtml -o out name.info --ignore-errors source


      4.3产生覆盖率文件,去除不需要的文件(include),或者包含需要的(source)
      转自:https://blog.csdn.net/hanshuai584044490/article/details/83374617
      问题:在产生了.gcno 和 .gcda两个文件后,使用lcov -c -d Debug/source/ -o Debug/coverage.info 产生中间文件coverage.info文件,然后用genhtml -o output/ Debug/coverage.info产生html文件,发现产生的index.xml文件包含了include,甚至/usr/*下的公共头文件,怎么去除这些不需要统计覆盖率的文件?

      1。正向提取需要的文件:

      //比如希望把source相关的路径提取出来

      lcov --extract Debug/coverage.info ‘source/’ -o Debug/finalresult.info

      2。反向去除不需要的文件:

      //比如希望去除UnitTest 和/usr/相关文件:

      lcov --remove Debug/coverage.info ‘UnitTest/’ ‘/usr/*’ -o Debug/finalresult.info

      使用:

      sudo lcov --remove helloworld_gcov0604.info  '/usr/*' 'Build/*' 'dbug/*' 'include/boost_1_59_0/*' '*/boost_1_59_0/*' -o remove06042.info
      

      使用过后,.info文件中也没有重复的行了!!!



      此处参考: https://blog.csdn.net/kevin_ji/article/details/50885680


      四、gcov检测代码覆盖率的原理

      原理概述

      Gcc中指定-ftest-coverage 等覆盖率测试选项后,gcc 会:
      * 在输出目标文件中留出一段存储区保存统计数据
      * 在源代码中每行可执行语句生成的代码之后附加一段更新覆盖率统计结果的代码,也就是前文说的插桩
      * 在最终可执行文件中进入用户代码 main 函数之前调用 gcov_init 内部函数初始化统计数据区,并将gcov_exit 内部函数注册为 exit handlers用户代码调用 exit 正常结束时,gcov_exit 函数得到调用,其继续调用 __gcov_flush 函数输出统计数据到 *.gcda 文件中

      说了这么多,其实还是很模糊,这里有几个要点需要深入
      • 怎么计算统计数据的?
      • gcov怎样插桩来更新覆盖率数据的
      • gcov_initgcov_exit怎样放到编译的可执行文件中的
      • gcno和gcda文件格式是咋样的

      只有把这几个问题搞明白了,才算真正搞懂gcov的原理.那么下面就来好好分析这几个问题

      1. gcov数据统计原理(即:gcov怎么计算统计数据的)

      gcov是使用 基本块BB跳转ARC 计数,结合程序流图来实现代码覆盖率统计的:

      • 1.基本块BB

      如果一段程序的第一条语句被执行过一次,这段程序中的每一个都要执行一次,称为基本块。一个BB中的所有语句的执行次数一定是相同的。一般由多个顺序执行语句后边跟一个跳转语句组成。所以一般情况下BB的最后一条语句一定是一个跳转语句,跳转的目的地是另外一个BB的第一条语句,如果跳转时有条件的,就产生了分支,该BB就有两个BB作为目的地。

    • 2.跳转ARC
    • 从一个BB到另外一个BB的跳转叫做一个arc,要想知道程序中的每个语句和分支的执行次数,就必须知道每个BB和ARC的执行次数

    • 3. 程序流图
    • 如果把BB作为一个节点,这样一个函数中的所有BB就构成了一个有向图。,要想知道程序中的每个语句和分支的执行次数,就必须知道每个BB和ARC的执行次数。根据图论可以知道有向图中BB的入度和出度是相同的,所以只要知道了部分的BB或者arc大小,就可以推断所有的大小。

      bb-arc-程序流图

      这里选择由arc的执行次数来推断BB的执行次数。

      所以对部分 ARC插桩,只要满足可以统计出来所有的BB和ARC的执行次数即可。

      2. gcov怎样插桩来更新覆盖率数据的

      当打开gcov编译选项是,在汇编阶段,插桩就已经完成,这里引用写的很好的一篇文章来说明:

      https://github.com/yanxiangyfg/gcov

      4. gcno和gcda文件格式

      https://github.com/tejainece/gcov


      五、服务程序覆盖率统计

      • 从 gcc coverage test 实现原理可知,若用户进程并非调用 exit 正常退出,覆盖率统计数据就无法输出,也就无从生成报告了。
      • 后台服务程序一旦启动就很少主动退出,用 kill 杀死进程强制退出时就不会调用 exit,因此没有覆盖率统计结果产生。

      为了解决这个问题,我们可以给待测程序增加一个 signal handler,拦截 SIGHUP、SIGINT、SIGQUIT、SIGTERM 等常见强制退出信号,并在 signal handler 中主动调用 exit 或 __gcov_flush 函数输出统计结果即可。

      该方案仍然需要修改待测程序代码,不过借用动态库预加载技术和 gcc 扩展的 constructor 属性,我们可以将 signalhandler 和其注册过程都封装到一个独立的动态库中,并在预加载动态库时实现信号拦截注册。这样,就可以简单地通过如下命令行来实现异常退出时的统计结果输出了:

      LD_PRELOAD=./libgcov_preload.so ./helloworld_server
      
      #或者:
      echo "/sbin/gcov_preload.so" >/etc/ld.so.preload
      ./helloworld_server
       
      • 1
      • 2
      • 3
      • 4
      • 5

      其中__attribute__ ((constructor))是gcc的符号,它修饰的函数会在main函数执行之前调用,我们利用它把异常信号拦截到我们自己的函数中. 【注:具体代码请看文章后面的例子章节】

      测试完毕后可直接 kill 掉 helloworld_server 进程,并获得正常的统计结果文件 *.gcda。

      六、内核和模块的gcov代码覆盖率测试

      • 从Linux内核2.6.31开始,gcov-kernel是Linux内核的一部分,可以不使用额外的补丁
      • 启用gcov-kernel配置选项:

        CONFIG_DEBUG_FS=y
        CONFIG_GCOV_KERNEL=y
        CONFIG_GCOV_PROFILE_ALL=y #获取内核数据覆盖率
        CONFIG_GCOV_FORMAT_AUTODETECT=y #选择gcov的格式
      • 编译,安装,启动内核,然后挂载debugfs: mount -t debugfs none /sys/kernel/debug
      • 内核相关文件介绍

      
      #支持gcov的内核在debugfs中创建如下几个文件或文件夹
      
      
      
      #所有gcov相关文件的父目录
      
      /sys/kernel/debug/gcov
      
      
      #全局重置文件:在写入时将所有覆盖率数据重置为零
      
      /sys/kernel/debug/gcov/reset
      
      
      #gcov工具理解的实际gcov数据文件。当写入文件时,将文件覆盖率数据重置为零
      
      /sys/kernel/debug/gcov/path/to/compile/dir/file.gcda
      
      
      #gcov工具所需的静态数据文件的符号链接。这个文件是gcc在编译时生成的, 选项:-ftest-coverage
      
      /sys/kernel/debug/gcov/path/to/compile/dir/file.gcno
       
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23

      需要注意的是/sys/kernel/debug 文件夹是一个临时文件夹,不存在于磁盘当中,是在内存当中存在的,其中的文件也是系统运行是动态产生的

      七、lcov工具使用

      • 安装lcov工具, 以ubuntu为例子: sudo apt install lcov,用于使gcno和gcda文件生成info覆盖率统计文件.
      • 在home目录下创建一个~/.lcovrc文件,并加入一行geninfo_auto_base = 1,用于消除ERROR: could not read source file错误

      八、info文件格式信息

      lcov生成的.info文件包含一个或多个源文件所对应的覆盖率信息,一个源文件对应一条“记录”,“记录”中的详细格式如下

      TN: <Test name> 表示测试用例名称,即通过geninfo中的--test-name选项来命名的测试用例名称,默认为空;
      
      SF: <File name> 表示带全路径的源代码文件名;
      
      FN: <函数启始行号>, <函数名>; <函数有效行总数>; <函数有效行总数中被执行个数>
      
      FNDA: <函数被执行的次数>, <函数名>; <函数有效行总数>; <函数有效行总数中被执行个数>
      
      FNF: <函数总数>
      
      FNH: <函数总数中被执行到的个数> BRDA: <分支所在行号>, <对应的代码块编号>, <分支编号>, <执行的次数> BRF: <分支总数> BRH: <分支总数中被执行到的个数> DA: <代码行号>, <当前行被执行到的次数> LF: < counts> 代码有效行总数 LH: <counts> 代码有效行总数中被执行到的个数 end_of_record 一条“记录”结束符
       
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25

      九、例子

      1. 合并不同用例的代码覆盖率

      #include <stdio.h>
      #include <string.h>
      
      int main(int argc, char *argv[])
      {   
          if (argc >=2) {
              printf("=====argc>=2\n");
              return;
          }
          printf("helloworld begin\n");
      
          if (argc <2){
              printf("=====argc<2\n");
              return;
          }
          return;
      }
       
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

      简单编写的Makefile如下:

      .PHONE: all
      all: helloworld
      
      CFLAGS+= -fprofile-arcs -ftest-coverage
      CC=gcc
      
      helloworld: *.c
          @echo ${CFLAGS}
          @${CC} ${CFLAGS} -o helloworld helloworld_gcov.c
      
      .PHONE: clean
      clean:
          @-rm helloworld 
      
       
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      单独产生同一个程序不同用例的info并合并

      make
      #运行两个参数用例并产生info文件和html文件
      ./helloworld  i 2
      lcov -c -d . -o helloworld2.info
      genhtml -o 222 helloworld2.info
      
      #运行无参数用例并产生info文件和html文件
      rm helloworld_gcov.gcda 
      ./helloworld
      lcov -c -d . -o helloworld1.info
      genhtml -o 111 helloworld1.info 
      
      #合并两个用例产生的info文件,输出同一个模块不同用例的总的统计数据
      genhtml -o 333 helloworld1.info helloworld2.info 
       
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      2. 服务程序无exit时产生gcda文件的方法

      helloworld_server_gcov.c的代码:
      #include <stdio.h>
      #include <string.h>
      #include <unistd.h>
      
      #include <stdlib.h>  
      #include <dlfcn.h>  
      #include <signal.h>  
      #include <errno.h>  
      
      int main(int argc, char *argv[])
      {   
          if (argc >=2) {
              printf("=====argc>=2\n");
          }
          printf("helloworld begin\n");
      
          if (argc <2){
              printf("=====argc<2\n");
          }
      
          while(1){
      
              printf("this is the server body");
              sleep(5);
          }
          return 0;
      }
      
       
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      编译helloworld_server_gcov.c的Makefile:
      ifeq ("$(PRJRELEASE)","gcov")
      CFLAGS+= -fprofile-arcs -ftest-coverage
      endif
      
      CC=gcc
      
      .PHONE: all
      
      all: helloworld
      
      helloworld: *.c
              @echo ${CFLAGS}
              @${CC} ${CFLAGS} -o helloworld_server helloworld_server_gcov.c 
      
      .PHONE: clean
      clean:
              @-rm helloworld_server helloworld_server_gcov.gcno helloworld_server_gcov.gcda
       
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      gcov_preload.c主要作用为捕获信号,调用gcov相关函数产生gcda文件。此文件编译成gcov_preload.so
      #include <stdio.h>
      #include <stdlib.h>
      #include <signal.h>
      #include <signal.h>
      #define SIMPLE_WAY
      
      void sighandler(int signo) 
      { 
      #ifdef SIMPLE_WAY
          exit(signo); 
      #else
          extern void __gcov_flush();     
          __gcov_flush(); /* flush out gcov stats data */
          raise(signo); /* raise the signal again to crash process */ 
      #endif 
      } 
      
      /**
      * 用来预加载的动态库gcov_preload.so的代码如下,其中__attribute__ ((constructor))是gcc的符号,
      * 它修饰的函数会在main函数执行之前调用,我们利用它把异常信号拦截到我们自己的函数中,然后调用__gcov_flush()输出错误信息
      * 设置预加载库 LD_PRELOAD=./gcov_preload.so
      */
      
      __attribute__ ((constructor))
      
      void ctor() 
      {
          int sigs[] = {
              SIGILL, SIGFPE, SIGABRT, SIGBUS,
              SIGSEGV, SIGHUP, SIGINT, SIGQUIT,
              SIGTERM     
          };
          int i; 
          struct sigaction sa;
          sa.sa_handler = sighandler;
          sigemptyset(&sa.sa_mask);
          sa.sa_flags = SA_RESETHAND;
      
          for(i = 0; i < sizeof(sigs)/sizeof(sigs[0]); ++i) {
              if (sigaction(sigs[i], &sa, NULL) == -1) {
                  perror("Could not set signal handler");
              }
          } 
      }
      
       
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      编译gcov_preload.c
      gcc -shared -fpic gcov_preload.c -o libgcov_preload.so
       
      • 1

      编译出libgcov_preload.so后拷贝到helloworld_server_gcov.c同目录下,然后在编译helloworld_server_gcov.c,最后运行,执行CTRL+c正常结束helloworld_server且产生了gcda文件。


      FAQ

      • 问题1

      ERROR: could not read source file /home/user/project/sub-dir1/subdir2/subdir1/subdir2/file.c
       
      • 1
      解决方法

      在home目录下创建一个~/.lcovrc文件,并加入一行geninfo_auto_base = 1

      出现此问题的原因是: 当编译工具链和源码不在同一个目录下时,会出现ERROR: could not read source file错误,这个geninfo_auto_base = 1选项指定geninfo需要自动确定基础目录来收集代码覆盖率数据.

    • 问题2

    • 使用lcov [srcfile]的命令生成.info文件的时候,提示如下错误, 无法生成info文件:

      xxxxxxxxxxxx.gcno:version '402*', prefer '408*'
      Segmentation fault
       
      • 1
      • 2
      解决方法

      在lcov工具中使用–gcov-tool选项选择需要的gcov版本,如lcov --gcov-tool /usr/bin/gcov-4.2


      参考


    展开全文
  • jacoco统计自动化代码覆盖率

    万次阅读 热门讨论 2018-07-10 13:40:07
    jacoco统计自动化代码覆盖率 1. 简介 1.1. 什么是Jacoco Jacoco是一个开源的代码覆盖率工具,可以嵌入到Ant 、Maven中,并提供了EclEmma Eclipse插件,也可以使用JavaAgent技术监控Java程序。很多第三方的工具...
  • JaCoCo 统计自动化测试代码覆盖率

    千次阅读 2018-12-13 17:49:14
    1. 为什么要关注测试覆盖率:代码...我们用代码覆盖率的结果来反向检查覆盖用例是否充分完整。 2. JaCoCo是什么: java主要代码覆盖工具:EMMA、JaCoCo JaCoCo是一个开源的覆盖率工具(官网地址: [https://www.j...
  • 如何分析 Python 测试代码覆盖率

    千次阅读 2020-05-02 11:40:00
    点击上方“编程派”,选择设为“设为星标”优质文章,第一时间送达!在测试中,为了度量产品质量,代码覆盖率被作为一种测试结果的评判依据,在Python代码中用来分析代码覆盖率的工具当属Cov...
  • iOS 增量代码覆盖率检测实践

    千次阅读 2018-12-27 19:58:00
    总第321篇2018年 第113篇本文介绍了对iOS覆盖率检测算法的研究,分享一种可以嵌入到现有开发流程中,并对开发透明的增量代码测试覆盖率工具的实现。到店餐饮技术部交易...
  • Java代码覆盖率历史发展轨迹

    千次阅读 2020-03-18 15:55:34
    这里写自定义目录标题0 覆盖率定义1 EMMA1.1 maven 集成1.2 与jenkins集成1.3 插桩坑总结2 jacocoJaCoCo优势JaCoCo基本概念JaCoCo 原理注入方式JaCoCo同时支持on-the-fly和offline的两种插桩模式On-the-fly插桩:...
  • 我们通常会将测试覆盖率分为两个部分,即“需求覆盖率”和“代码覆盖率”。 需求覆盖:指的是测试人员对需求的了解程度,根据需求的可测试性来拆分成各个子需求点,来编写相应的测试用例,最终建立一个需求和用例的...
  • 本文将介绍两种开发实践,用于提高 Java 单元测试中的代码覆盖率代码覆盖率 = (被测代码 / 代码总数)* 100%。提高被测代码数量或降低代码总数,均可达到提高代码覆盖率的效果。在本文中,您将看到如何通过使用...
  • 代码覆盖率检测正是帮助开发、测试同学提前发现问题,保证代码质量的好帮手。 对于开发者而言,代码覆盖率可以反馈两方面信息: 自测的充分程度。 代码设计的冗余程度。 尽管代码覆盖率对代码质量有着上述好处,但...
  • 代码测试覆盖率Cobertura使用

    万次阅读 2017-07-17 17:47:26
    软件测试中,代码覆盖率是一项衡量是否所有代码都被测到的指标,下面以maven项目为例说明Cobertura工具的使用。(当然还有其他的代码覆盖率测试工具,Emma、Gcov、Lcov等,可以按自己的需求选择合适的工具)1.在...
  • C++语言的单元测试与代码覆盖率

    千次阅读 2018-11-20 11:32:44
    直接交付没有经过测试的代码是不太好的,因为这很可能会浪费整个团队的时间,在一些原本早期就可以发现的问题上。而单元测试,就是发现问题一个很重要的环节。 本文以C++语言为基础,讲解如何进行单元测试并生成测试...
  • 1. 什么是代码覆盖度代码覆盖率分析实际上一种度量方式,间接度量质量的方法的过程,是在保证测试质量的时候潜在保证实际产品的质量,在程序中寻找没有被测试用例测试过的地方的流程,创建新的测试用例来增加覆盖率...
  •  而.Net Core中如何在测试时计算代码覆盖率呢?如果使用VS的企业版,那么VS自带了代码覆盖率分析工具:      详情参考:...
  • 谈谈测试覆盖率

    千次阅读 2019-10-24 14:06:27
    以前面试的时候,两次被问到同一个问题,“你是...二、偏向技术的代码覆盖率; 需求覆盖率:指测试对需求的覆盖程度,通常的做法是将软件需求分解成多个测试任务,通过计算完成的测试任务,来得出需求覆盖率; 需求...
  • 单元测试之覆盖率浅谈

    千次阅读 热门讨论 2015-02-16 17:03:58
    一般我们用工具做的代码覆盖率计算方法是:   代码覆盖率 = 被测代码行数 / 参测代码总行数 * 100%   二、度量方式     代码覆盖程度的度量方式是有很多种的,这里介绍一下最常用的几种:   1. 语句覆盖/行覆
  • UT覆盖率

    万次阅读 2013-11-15 10:41:28
    1 单元测试覆盖率与CI2 什么是代码覆盖率? 2.1 单测技术框架简介2.2 JUnit:2.3 TestNG:2.4 maven2.5 sonar2.6 eclipse2.7 Jenkins2.8 Cobertura:2.9 构建UT测试持续化集成环境及覆盖率监控 ...
  • 测试覆盖率

    千次阅读 2013-07-22 10:17:07
    覆盖率是度量测试完整性的一个手段,是测试有效性的一个度量。 编辑本段评测方法 测试覆盖是对测试完全程度的评测,它建立在测试覆盖基础上,测试覆盖是由测试需求和测试用例的覆盖或已执行代码的覆盖表示的。 质量...
  • 常用java代码质量检查工具.md

    千次阅读 2019-11-22 22:51:34
    PMD-CPD :Copy/Paste Detector (CPD) 代码重复检测 PMD:分析 Java 源代码,找出潜在的 bug Findbugs :Java bug检查 checkstyle : 固定团队编码风格,固定命名风格以及换行风格等 P3C: 阿里巴巴Java规范,这个...
  • Java覆盖率工具jacoco,Cobertura

    千次阅读 2020-02-11 11:03:48
    最近研究Java覆盖率工具,了解到了jacoco,Cobertura这两款,...市场上主要代码覆盖率工具: Emma Cobertura Jacoco Clover(商用) 具体见下表: 工具 Jacoco Emma Cobertura 原理 使用 A...
  • 计算机常用算法对照表整理

    千次阅读 2017-07-26 10:58:01
    常用对照:NLP CRF算法: 中文名称条件随机场算法,外文名称conditional random field algorithm,是一种数学算法,是2001年提出的,基于遵循马尔可夫性的概率图模型。 全部对照第一部分、计算机算法常用术语中英...
  • SourceMonitor的复杂度计算方法

    千次阅读 2014-03-25 13:23:09
    圈复杂度 下面科普性的内容来自: ...它可以用来衡量一个模块判定结构的复杂程度,数量上表现为独立现行路径条数,也可理解为覆盖所有的可能情况最少使用的测试用例数。圈复杂度大说明程序代码的判断逻辑复杂
  • 圈复杂度(Cyclomatic Complexity)是一种代码复杂度的衡量标准。它可以用来衡量一个模块判定结构的复杂程度,数量上表现为... 下面这个实例中,单元测试的覆盖率可以达到100%,但是很容易发现这其中已经漏掉了一个N...
  • 确实wx目前还没有测试,对于一个开源项目来说,没有测试和代码覆盖率是不完善的,而且从技术选型来说,大多是不敢选的。 那么Nodejs开源项目里怎么样写测试、CI和代码测试覆盖率呢? 测试 目前主流的...
  • 计算机视觉的常用图像处理技术

    万次阅读 2020-05-21 22:38:59
    本篇将介绍常用基于OpenCv等视觉库的影像分割以及图像处理技术,并且附赠源码 一:边检测器 先复习一下基础的OpenCv操作:如上图:第一张是原图; 第二张是灰度图: gray = cv2.imread('lena.jpg', cv2.IMREAD_...
  • 深度学习常用优化方法

    千次阅读 2017-10-07 10:36:29
    【本文转载自机器之心 翻译:沈泽江 原文地址:http://www.jiqizhixin.com/article/1857】梯度下降法,是当今最流行的优化(optimization)算法,亦是至今最常用的优化神经网络的方法。本文旨在让你对不同的优化...
  • AUC值经常用来评价一个二分类模型的训练效果,对于许多机器学习或者数据挖掘的从业者或在校学生来说,AUC值的概念也经常被提起,但由于大多数时候我们都是借助一些软件包来训练机器学习模型,模型评价指标的计算往往...
  • python代码库-Python常用库大全

    千次阅读 2020-10-29 23:20:33
    Python常用库大全,看看有没有你需要的。环境管理管理 Python 版本和环境的工具p – 非常简单的交互式 python 版本管理工具。pyenv – 简单的 Python 版本管理工具。Vex – 可以在虚拟环境中执行命令。virtualenv –...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 45,166
精华内容 18,066
关键字:

代码覆盖率常用的计算方法