初识代码覆盖率

在我们正式了解GO语言代码覆盖率实现之前,我们有必要先来了解一下什么是代码覆盖率?

定义:代码覆盖是软件测试中的一种度量手段,是一种白盒测试方法,描述程序中源代码被测试的比例和程度,所得比例就称之为代码覆盖率。我们在做测试时,代码覆盖率常常被用来作为衡量测试好坏的指标。甚至用代码覆盖率来考核测试任务完成情况,比如,代码覆盖率必须达到70%或 80%等。

从覆盖源代码语句的详尽程度分析,包括以下不同的覆盖标准:语句覆盖、判定覆盖、条件覆盖和路径覆盖

语句覆盖:又称行覆盖(LineCoverage),段覆盖(SegmentCoverage),基本块覆盖(BasicBlockCoverage),这是最常用也是最常见的一种覆盖方式,就是度量被测代码中每个可执行语句是否被执行到了。选择足够多的测试数据,使被测程序中每条语句至少执行一次。语句覆盖是很弱的逻辑覆盖,它只管覆盖代码中的执行语句,却不考虑各种分支的组合等等。

判定覆盖:又称分支覆盖(BranchCoverage),所有边界覆盖(All-EdgesCoverage),基本路径覆盖(BasicPathCoverage),判定路径覆盖(Decision-Decision-Path)。比语句覆盖稍强的覆盖标准,它度量程序中每一个判定的分支是否都被测试到了。设计足够的测试用例,使得程序中的每个判定至少都获得一次“真值”或“假值”,或者说使得程序中的每一个取“真”分支和取“假”分支至少经历一次,因此判定覆盖又称为分支覆盖。



条件覆盖:它度量判定中的每个子表达式结果true和false是否被测试到了。在设计程序中,一个判定语句是由多个条件组合而成的复合判定。为了更彻底地实现逻辑覆盖,可以采用条件覆盖(Condition Coverage)的标准。条件覆盖的含义是:构造一组测试用例,使得每一判定语句中每个逻辑条件的可能值至少满足一次。条件覆盖与判定覆盖非常容易混淆,条件覆盖不是将判定中的每个条件表达式的结果进行排列组合,而是只要每个条件表达式的结果true和false测试到了就OK了。因此,我们可以这样推论:完全的条件覆盖并不能保证完全的判定覆盖。


路径覆盖:又称断言覆盖(PredicateCoverage)。它度量了是否函数的每一个分支都被执行了。就是所有可能的分支都执行一遍,有多个分支嵌套时,需要对多个分支进行排列组合,可想而知,测试路径随着分支的数量指数级别增加。路径覆盖被很多人认为是“最强的覆盖”。



代码覆盖率又有什么意义?我们为什么要统计代码覆盖率?

  • 分析未覆盖部分的代码,从而反推在前期测试设计是否充分,没有覆盖到的代码是否是测试设计的盲点,为什么没有考虑到?需求/设计不够清晰,测试设计的理解有误,工程方法应用后造成的策略性放弃等等,之后进行补充测试用例设计。
  • 检测出程序中的废代码,可以逆向反推在代码设计中思维混乱点,提醒设计/开发人员理清代码逻辑关系,提升代码质量。
  • 代码覆盖率高不一定能说明测试质量就一定高,但是反过来看,代码覆盖率低的,测试质量就一定是比较薄弱的,可以作为测试自我审视的重要工具之一。


GO语言的代码覆盖率实现


Go自带了测试覆盖率工具,无需安装任何新包就能实现覆盖率信息的收集。

Go的测试覆盖率工具,在编译之前会重写包的源代码,通过先埋点,再编译,然后再运行代码,并且在这个过程中存储统计覆盖信息。

我们简单的创建一个单元测试的例子来看一下,如何得到GO的覆盖率数据。

1、首先创建目录 test_cover,在其中创建如下2个文件:

测试文件:


PS:测试文件名必须以"_test.go"结尾;方法名必须以"Test"打头,并且形参为 (t *testing.T)

在测试文件中,我们设置了2条测试数据,score分别为50和80的情况


go test -cover


coverprofilecover


4、我们可以分析该文件,得到每个function的覆盖率,使用命令:



5、我们也可以用一个更清晰的HTML页面来显示结果go tool cover -html=coverage.out

实际工作中我们除了要收集单元测试的覆盖率情况之外,还需要收集所有自动化用例或手工用例执行完后代码覆盖率情况,以此来鉴别我们的测试用例设计质量。那又需要怎么实现呢?

其实思路是一样,需要在入口main函数上建立测试文件,如下方式:


1、创建main_test.go文件

看一下自己代码的main()函数所在的go文件名称,直接命名为*_test.go文件即可; 比如代码文件名 main_server.go, 可直接命名为main_server_test.go,并放在同一目录下。

main_test.go文件:

注:同时需要特别查看一下main函数, 如果其中存在os.Exit(),需要更改为return (根据实际情况分别为return/return 0/return 255...),目的很简单希望停掉服务的时候,main函数不要直接退出,而是要return 到测试方法中,生成覆盖率文件。


2、编译代码,打包执行文件

Go语言正常的编译打包命令是go build, 但在这里我们需要一个基于测试覆盖率的执行包,所以用go test的方式来打包得到。


  • -c 表示 生成测试二进制文件
  • -covermode=count 表示 生成的二进制中包含覆盖率计数信息

在go语言的测试覆盖率统计时,go test通过参数covermode的设定可以对覆盖率统计模式作如下三种设定


  • -coverpkg 后面是要统计覆盖率的文件源码目录
  • -o 后面是输出的二进制文件名(默认为 packetname.test)


3、启动服务

需要在原始的启动命令后增加加以下参数,方便生产覆盖率统计文件:

-systemTest -test.coverprofile coverage/coverage.cov

  • -systemTest 用来启动前面说过的main test
  • -test.coverprofile 用来指定覆盖率信息写入到哪个文件


4、统计覆盖率

  • 执行所有自动化用例
  • 停掉服务后,目录中会生成覆盖率文件coverage.cov


  • 通过下列命令可生成html测试报告, 整个覆盖率的收集工作就完成了。