0


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

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

什么是代码覆盖率

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

但是没有覆盖到的代码就一定有问题吗?不一定有问题,但要知道,一个完整的测试运行之后,有哪些代码没有跑到。这些代码有可能是有问题的,也有可能是没有问题的,有可能是冗余的,也有可能架构设计有问题,都有可能。我们不能说它是不好的代码,但至少是有问题的代码,这是我们做代码覆盖率的目的,而非去度量某一个质量指标。

做代码覆盖率比较容易犯的一个误区,会觉得代码覆盖率到达了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的不同点,来看是否被统计,我们要二次渲染、修改这个报告。

以上就是我们今天分享的所有内容了,给大家展示了什么是代码覆盖率,代码覆盖率的价值是什么,怎么做单元测试代码覆盖率的集成,以及怎么做功能测试代码覆盖率的简单演示。

(谢绝转载,更多内容可查看我的专栏)


本文转载自: https://blog.csdn.net/daopuyun/article/details/143757763
版权归原作者 daopuyun 所有, 如有侵权,请联系我们删除。

“单元测试和功能测试代码覆盖率实操详解”的评论:

还没有评论