单元测试的最终统计标准就是单测覆盖率,统计单测总体覆盖了多少行代码。一般来说,我们只需要关注增量代码的覆盖率,而非全量代码。增量代码就是本次迭代改动的代码,比如本次迭代改动了100行代码,我们保证单测能覆盖到这 100 行代码就行。
在命令行覆盖率的统计指令,会生成一个 coverage 文件,基于这个 coverage 文件,我们结合 go tool 工具,就可以图形话展示代码覆盖率情况。
go test -coverprofile=coverage.out -covermode=atomic ./...
指令中 ./… 是 go 的标准用法,会递归执行目录下的所有单测文件,最终,生成的 coverage 文件会保存在属性 -coverprofile 指定的文件中。而我们通过下面的指令就可以图形化展示覆盖率
go tool cover -html=coverage.out
通过这种方式,我们选取一个简单的单测,执行上面的两个指令,会在浏览器中展示下图的示例,其中,绿色的代码表示被单测覆盖,红色表示未被覆盖,灰色表示没有做覆盖率埋点。关于覆盖率埋点,下面单测文件的覆盖率是 33.3%,也就是有 1/3 的代码被覆盖了。
表面上看的话,下面有效的代码总共有3行,其中1行被覆盖到了,所以,覆盖率是 33.3%
新增代码覆盖率没有被统计
在统计覆盖率的过程中,通过断点调试,我肯定单测一定走到了新增代码的逻辑里,但是新增代码的覆盖率却没有被统计到,我觉得很奇怪。在 goland 编译器中加载 coverage.out 文件分析,新增代码覆盖率也没有被统计到。下面是代码的结构目录:
├── service
│ └── point.go
├── controller
│ ├── endpoint.go
│ └── endpoint_test.go
在 endpoint_test 中的单测只能覆盖到 controller 包中的代码,尽管 endpoint 中的代码引用到了 service 包中的 point 文件,但是 point 中的代码覆盖率并不能被统计到。这样的结果其实也可以理解,单测的对象应该是包中的局部方法,不统计依赖包中的方法也是合理的做法。
covermode 属性
我有好奇 covermode 这个属性的意图是什么,在The cover story中有这个属性的介绍,子标题是 heat map,热力图。当我们不仅仅想知道代码行是否被执行,还想知道代码被执行了多少次,就要靠这个属性。
如果不指定这个属性,默认值是 set,表示只统计语句是否被执行。另外的两个属性count、atomic 被用来统计语句被执行的次数信息。这两个属性的差异,文章中也有介绍。atomic 属于原子计数,统计结果会更加精确。
- count: how many times did each statement run?
- atomic: like count, but counts precisely in parallel programs