单元测试和功能测试代码覆盖率实操详解

本文将从代码覆盖率的价值所在、软件单元测试代码覆盖率、功能测试代码覆盖率这三个方面进行展开。

什么是代码覆盖率
代码覆盖率就是运行测试之后,代码被覆盖到了多少,哪些代码跑了,哪些没有跑。根据运行测试手段不同,代码覆盖率分成了单元测试代码覆盖率、接口测试代码覆盖率和功能测试代码覆盖率。代码覆盖率最终的目的是找到那些没有被覆盖到的代码。

但是没有覆盖到的代码就一定有问题吗?不一定有问题,但要知道,一个完整的测试运行之后,有哪些代码没有跑到。这些代码有可能是有问题的,也有可能是没有问题的,有可能是冗余的,也有可能架构设计有问题,都有可能。我们不能说它是不好的代码,但至少是有问题的代码,这是我们做代码覆盖率的目的,而非去度量某一个质量指标。
做代码覆盖率比较容易犯的一个误区,会觉得代码覆盖率到达了80%就说明没有问题。就像做性能测试的时候,TPS上到了多少,CPU使用率到达了多少就一定怎样。我们经常说,抓一些数据的把手是一个很不好的习惯,仅仅是找一些指标让自己心里变得舒服而已。比如说指定CPU使用率到达75%就一定怎样了,拿着这根线去卡一切,这样很多事就变得简单了,但实际工作中并非如此简单。

上面这张图的意思是发现没有被覆盖到的代码,而不是简单的做一些质量的标准。代码覆盖率可以帮助我们: 1、分析未覆盖部分的代码,从而反推前期测试设计是否充分,没有覆盖到的代码是否是测试设计的盲点,为什么没有考虑到?是需求/设计不够清晰,测试设计的理解有误,之后进行补充测试用例设计。 2、检测出程序中的废代码,可以逆向反推在代码设计中思维混乱点,提醒设计/开发人员理清代码逻辑关系,提升代码质量。 3、代码覆盖率高不能说明代码质量高,但是反过来看,代码覆盖率低,代码质量不会高到哪里去,可以作为测试自我审视的重要工具之一。

代码覆盖率统计指标

我们可以从多个维度去考虑代码的覆盖情况,我们先来看第一个纬度,语句的覆盖。 

语句覆盖(StatementCoverage)
语句覆盖(StatementCoverage)又称行覆盖(LineCoverage),度量被测代码中每个可执行语句是否被执行到了。这里说的是“可执行语句”,不包括头文件声明,代码注释,空行等。只统计能够执行的代码被执行了多少行。简单来说,就是这行代码只要被覆盖到了就是1,没有被覆盖到就是2。

我们先来看上面这段代码,这里有价值的语句一共有3条语句:判断语句是一条,return 0是一条,return 1是一条。很多代码覆盖率会认定else不算,因为它就是一个关键字,实际上没有什么语句的价值。
如果每一行都执行到了,行覆盖率就是1,总共3行,3行都执行到了,行覆盖率就是100%。稍微有点代码常识的人看到这里就能看出问题了,如果这个if语句我确实执行到了,但是它明显地有判断条件。
如果第一个判断条件执行了,就能判断出这个语句的真假,后面的判断条件不执行了,继续往下走。这个语句确实叫执行了,true/ false 都没有完全覆盖到而已,所以我们说行覆盖是最弱的代码覆盖。
语句覆盖常常被人指责为“最弱的覆盖”,它只管覆盖代码中的执行语句,却不考虑各种分支的组合等等。
判定覆盖(DecisionCoverage)

除了行覆盖、语句覆盖之外,还有判定覆盖,后面我们有详细的例子。什么叫判定覆盖?判定覆盖也叫分支覆盖(BranchCoverage),度量程序中每一个判定的分支是否都被测试到了。
这很好理解,比如我们刚才的例子里有一个if语句,行覆盖就是一行行往下走,判定覆盖是指有分支的语句,每一个语句是否都被走到了。我们再来一起看一下这个例子。

这个例子完整地有if有 else,所以行一旦覆盖成功了,三句都走完了,那么分支覆盖肯定也是百分之百走完,也是成功的,因为if走了,else也走了。
我们试想一下,如果把下面的else盖住的话,没有这个else,只有if,如果语句覆盖里面的if走到了,return走到了,行覆盖率是百分之百,但是else的条件并没有测到,这个时候只覆盖到了if为true的情况,没有覆盖到else分支,虽然else分支里面没有代码。
这个例子就说明判定覆盖和行覆盖还是有本质区别的。
条件覆盖(ConditionCoverage)
它度量判定中的每个子表达式结果true和false是否被测试到了。为了说明判定覆盖和条件覆盖的区别,我们来举一个例子,假如被测代码如下:
在刚才的例子里,如果if、else都走到了,那么分支覆盖肯定也是百分之百,但是条件覆盖不一定是百分之百。if整体为true,确实走到了,if整体为false,也就是else,确实也走到了,对于if判断语句来说,两个分支确实都走到了。
但这里面还有一个问题,if后面是有两个判定条件的,稍微有点代码常识的朋友都知道,不管什么语言,都有逻辑判断短路的问题。两个and判断,如果有一个and判断为false了,后面就不会判断了,所以那个就不走了,不管走不走,并没有覆盖到两个并列条件,两个子条件都为true或都为false的情况并没有走到。
整体的判断语句走到了,但是里面两个具体的分支判断条件分别的true、false没有走到。这个时候分支覆盖率是百分之百了,但是条件覆盖率并不是百分之百。
路径覆盖(PathCoverage)
它度量了是否函数的每一个分支都被执行了。有多个分支嵌套时,需要对多个分支进行排列组合。因为现在只有一个if条件,所以不好看路径。举个例子,比如两个判定条件,两个if,只覆盖了第一个if判定条件的true和false以及第二个判定条件的true和false,但是两个并没有分别来走。
比如第一个是true,第二个是false,或者第一个是false,第二个是true的这种交叉情况。也就是没有把包含所有的if、else条件的整体当成一个完整的路径去处理,这个路径就有多种组合了。这就是路径覆盖。
下面再通过一个小例子去详细解说一下。

上面是一个java的语句,一个foo函数,有a、b两个整型,一个if条件,if条件里面有两个小的条件。接下来有一个else分支语句,最后有一个return语句。有一组判断,两个分支,三个语句(不算定义的话)。
TestCaes1: a=5,  b=任意数字  
当a=5的时候,a<10这个条件成立,对于逻辑或来说,前面的条件为真,后面的条件就不用判断了,必然是为真的。所以我给出的第一组测试用例,当a=5,  b=任意数字时,它都会走到分支一,我们再给一个值。
TestCaes2: a = 15,b =  15          
当a=15时,a<10不成立,进而去判断第二个条件,当b=15,b<10也不成立的时候,会走到else这里,这样也覆盖了分支二。
语句覆盖100%,分支覆盖也是100%,但是里面小条件的true和false并没有很完整地被覆盖。a<10为true、为false和b<10为true、为false的情况没有分别覆盖,因为逻辑短路的原因,所以你不能让它短路。
 完全的条件覆盖:
TestCase1: a = 5,b = 任意数字  ture X 

TestCase2: a = 15, b = 5            false, true

 TestCase2: a = 15, b = 15          false, false 
第一条,先让a=5,b=任意数字,这个时候a<10这个条件为true的情况已经被覆盖。接下来通过a = 15, b = 5和a = 15, b = 15  这两组数据,让a<10这个条件为false时,b<10这个条件的true和false,都覆盖到了。
在这个逻辑或的条件中,每个分支的true和false都覆盖到了,这个时候才完成了我们所说的条件覆盖。

下面我们再一起来看一个例子。

 对于这样两个并列的if条件来说,怎么来写语句测试用例以做到语句覆盖率100%、判定覆盖率100%、条件覆盖率100%、路径覆盖率100%。
TestCase a = 5, b = 5   nReturn = 11

语句覆盖率100%
TestCase1 a = 5,  b = 5    nReturn = 11

TestCase2 a = 15, b = 15   nReturn = 0

判定覆盖率100%    条件覆盖率100%
TestCase1 a = 5,    b = 5     nReturn = 0

TestCase2 a = 15,   b = 5     nReturn = 1

TestCase3 a = 5,    b = 15    nReturn = 10

TestCase4 a = 15,   b = 15    nReturn = 11

路径覆盖率100% 

从这里可以看出,如果想达到路径覆盖率100%的话,测试用例一定是比前三种要多的。所以我们最终得出的结论是:路径覆盖率 > 判定(分支)覆盖 > 语句覆盖。
下面的内容就进入到我们本次分享的重点,代码覆盖率的工具。

Jacoco工具演示

大家去它的官网就可以下载源码包,把下载的Jacoco源码包进行解压,我们会进入到一个叫做doc的一个文件夹。这个文件夹里有一个example文件夹。
进去之后,有一个build文件夹。这是一个项目,是官方给我们举的一个例子。这个例子是很简单的几个java代码文件,然后给你写了一段单元测试,让你通过运行单元测试的方式去运行这个项目。
比如说你的项目里有两个文件,一个是src文件,还有一个文件是测试用例,我们称之为test文件。这个src需要先编译成.class,然后进行打包编译。由java文件编译成一个.class文件,再编译成可执行的文件去执行。
假如说我们的项目是Maven执行起来的,我们的项目执行起来之后,我们运行单元测试的脚本,运行单元测试的脚本就肯定会访问到业务代码,这个时候jacoco在同步统计。因为这个项目里面集成了Jacoco,统计后会生成一个中间文件,然后最后讲这个中间文件生成报告。
项目打包编译完成了,单元测试也运行完成了,单元测试运行到底测到了哪些代码也被统计出来了,在项目指定的一个路径里面。接下来我们就一起演示一下。
本文整理自优品软件培育计划公益直播活动以下内容为工具演示部分,私信获取回放链接,获得更直观的学习效果)
我们可以看到这个build文件夹里有好几个文件,我们都知道build.xml是Ant的构建文件,pom.xml是Maven的构建文件,我们先来跑Ant的构建文件。
打开一个cmd的窗口命令,进入到这个项目里。执行Ant,我们就不执行Ant build了,大家都知道,我们在执行Ant的时候,它会去找当前路径下的build.xml的文件。

 这个build.xml文件里写的是对整个Java工程进行代码的编译,代码的打包执行,统计代码覆盖率,并且出局一个Jacoco的报告。它都写在这里面了,你自己也可以把你的项目里面也加上Jacoco的代码统计,学着官方的build文件自己写一个,完全是可以运行的,我们跑一下试一试。

我们可以看到,先进行了clean,又进行了compile编译,又进行了单元测试,里面是它打印出的一些逻辑,很简单的一些单元测试的用例,一些加减乘除的计算,然后根据jacoco的执行文件出报告。
最后我们看到build也成功了,报告也出来了,接下来咱们一起看看报告。
这个报告是官方写死的一个报告,但是你可以自己去配置文件中修改。我们可以看到在咱们的构建文件中生成了一个叫target的文件夹,在set里面有一个叫jacoco的报告,里面有html的报告,也有xml的报告。
我们打开index这个报告来看一下,里面有两个包。它里面有行覆盖,分支覆盖,语句覆盖等不同覆盖的维度,绿色的表示被覆盖了,红色的表示没有被覆盖。
我们点开其中一个,这里面有两个类,展示的是每个类的覆盖情况。
里面有几种方法,这里面是每个方法的覆盖情况,如果你觉得还不够,我们可以再把方法点开。
点开之后我们可以看到里面就到具体的代码的覆盖的情况了。
以上就是Jacoco的一个基本的流:运行、编译、执行单元测试、统计、出报告。
我们再演示一下如果运行pom.xml的情况,很多朋友现在公司里已经都不用Ant了,用的是Maven,下面我们一起看看用Maven的情况。
最后我们可以看到总共用了7秒多的时间,出的报告还在刚才上面说的那个文件夹,因为它们俩写的路径是一样的。我们可以看到结果界面也都是一样的。
这就是我们所说的单元测试的覆盖率。很多测试的朋友可能会不太关心,因为很多时候这个测试脚本是开发做的。一般的单元测试,咱们的测试人员可能不会去编写测试用例,但是我们可以帮助开发去辅助执行。

 代码覆盖率在DevOps中的应用
单元测试不可能一直本地化执行,要找一个Jekenis,把代码传到gitte、github、gitlab等代码仓库中。在Jenkins上配一个任务,把代码拉取下来之后,进行打包、编译、出覆盖率报告,出完了覆盖率报告之后,推送到Jenkins前段去进行展示。
这个对于开发人员来说可能都比较熟悉了,咱们测试人员接触的可能少一些,但是我们要知道这个原理。总体来说就是把代码放到Jenkins上去,拉取之后先运行,编译,执行单元测试,统计代码覆盖率,然后代码覆盖率到达一个值之后,才允许你进行下一步的构建,这也是现在很多流水线上都做的一个操作。
下面是两个很简单的例子,拉取代码,执行shell文件,执行之后出报告,出报告之后把这个代码跑起来,在服务的架设上把代码的覆盖率推送给前端去展示。

 前面说了单元测试的覆盖情况,我们现在说一下功能测试的覆盖情况。这里说的功能测试其实也包含接口测试。回到一开始我们提到的内容,运行测试的方式不同,覆盖率关心的东西也不同。
刚才说的是运行单元测试用例,所以我们关心的是单元测试的覆盖率。现在我们说的是接口测试,运行的是接口测试的测试用例,统计的也是覆盖率,我们叫接口测试测到的代码覆盖率情况。
但实际上统计的都是代码。如果再进一步,测的是功能,在页面上点点点,那我们最终统计的是功能测试的代码覆盖率情况。最终的落脚点还是统计代码,只不过测试手段不一样。不管是接口还是功能,其实都一样。
有很多朋友会问,接口测试代码覆盖率是什么意思?接口测试代码覆盖率也是代码覆盖率,只不过你运行测试用例的手段是运行接口测试用例,所以没有区别。
操作流程:gitte拉取---> maven编译、打包--->启动event.jar并添加jacoco-agent监听 --->关联接口自动化项目开始执行--->向event项目发送dump指令--->生成jacoco覆盖率报告
下面我们来演示一下。我现在找了一台Linux主机,在这个主机上提前放好了一个项目。

这个项目是我以前写的一个做性能测试和持续集成时候的项目。这个项目是一个纯接口的一个项目,没有页面。执行Maven命令的时候,会把它打包成一个jar包,先把这个jar包运行起来。

一运行这个jar包就相当于这个Java的服务也就起来了,但是在运行这个服务的同时,把Jacoco也执行起来,换句话说就是启动这个服务的同时,让它不停地监听、监控代码的覆盖统计的情况。

是怎么监控的呢?一个是对程序里的java配置进行一些小的变更,其实不用污染代码,需要加一个jar包就可以,加一些依赖。加好之后,启动项目的时候要加一个额外的辅助命令:
nohup java-javaagent:jacocoagent.jar=includes=*,output=tcpserver,address=192.168.0.188,port=12345 -jar event-0.0.1-SNAPSHOT.jar &
这个jacocoagent.jar的包去哪下呢?刚才咱们展示的官方的那个包里就有这个jar包。这个jar包放在服务器上,启动项目的同时,连同这个jar包一起启动。
并且对外指定一个协议类型是tcp协议,地址是服务器的内网的地址,端口号随便写一个,比如说示例这里写的是12345,Jacoco的官方也是推荐使用12345,它的示例里面也是这样写的。

也就是说我在启动原始的服务的同时,启动一个server,这个server是用来出代码覆盖率报告的,也就是说我们同时启动了两个服务,一个是我的服务本身,还有一个是代码覆盖率统计的服务,这个服务依然在本地,端口号是12345。

放在服务器之后,我们可以看到大概花费了10秒钟的时间把它起来了。起来之后,去访问一个接口,然后看一下代码覆盖率到底覆盖了哪些代码。

然后我们在这个项目的本地,找到jacococli.jar,我们用这个包来出覆盖率的报告。刚才的服务起来了,并且开了一个端口号,用于出覆盖率,现在就应该访问刚才那个地址和12345端口,把刚才报告统计的信息输出出来。

dump结果文件:
现在执行第二个命令:java -jar jacococli.jar dump --address localhost --port 12345 --destfile ./jacoco-demo.exec
注意我们不是在一台电脑上执行的,这个是在本地执行的,填的是刚才那台服务器的外网地址,12345端口。出一个覆盖率的报告,这个文件就放在当前的路径下就可以。
它的含义是,访问远端的服务端口,出具这一段时间(从代码起来到目前为止)的代码覆盖的情况。 生成报告:java -jar jacococli.jar report ./jacoco-demo.exec --classfiles ./target/classes --html ./report
有了这个路径了,接着要出一个覆盖率的报告了,还在这个路径下,通过刚才那个文件生成了一个报告,把报告保存在当前路径下一个叫report的文件夹里,这叫远端出报告。
我们一起看一下这个报告,先来看一下controller控制器里面的东西。
可以看到有这么多的接口,其中有一个接口叫 findAllCity,这个接口被测到了,所以这个接口是百分之百。其他的接口都没有被测到,那我们现在再测一个,findCityByName。
再访问这个接口的地址,重复刚才的动作,再出一个报告,看看是否有变化,可以看到报告已经跟上面的不一样了。

也就是说,只要我们不停地去测,就可以看到在测接口的过程中代码覆盖率的情况了。
很多朋友可能会问了,如果要是测功能测试,页面测试呢?
现在测的是接口,假设现在有一个页面,我们在页面上点点点,出来一个报告,报告里同样会展示哪里被测到了,哪里没有被测到,测试过多少代码都可以被看到。
问题又来了,有时候开发测试团队并没有遵循那么标准的流程,经常先上一版,都改完后再上一版。这就是我们经常所说的全量覆盖率对于日常迭代,尤其是敏捷开发模型下其实是没有什么价值的。
张三可能在下午4点的时候测了一版,他的覆盖率是80%,李四是一个开发,就改了一点代码,张三没有必要把所有代码都测一遍,他只测李四的修改就可以了,这个时候他的代码覆盖率可能从第一个全量版本的80%一下子跌到下一个版本可能只有10%。
所以说我们在很多实际情况下要统计增量代码的覆盖情况,这个就需要我们自己来写脚本,来做代码库的diff,来对比两个代码库diff的不同点,来看是否被统计,我们要二次渲染、修改这个报告。
以上就是我们今天分享的所有内容了,给大家展示了什么是代码覆盖率,代码覆盖率的价值是什么,怎么做单元测试代码覆盖率的集成,以及怎么做功能测试代码覆盖率的简单演示。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值