在一个流程规范的项目里,如果想发布一行代码到生产环境,都需要完整的测试流程,如果是向外网发布一个功能或版本,那么更需要进行完整的功能测试,其中就包括白盒测试和黑盒测试。尤其是在游戏行业,因为玩法逻辑复杂,很多逻辑很难考虑到,因此覆盖率验证是一个必备的一个流程。比如现在的团队就要求放出的游戏版本的代码覆盖率需要达到100%才算是达到发版标准,因此覆盖率测试在保证代码质量上扮演者重要角色。我们通过覆盖率可以看到我们的代码有哪些是已经跑到的,有哪些是特殊情况绕过了尚未验证,因此通过代码覆盖率我们可以对整个代码逻辑有充分的认识和判断。这篇文章主要介绍如何对Go的代码建立覆盖率,而Go的覆盖率建立依赖go test和go tool cover。


一个简单的项目建覆盖率


首先看下项目组织,一共有两个文件,main.go是我们的实现的程序,main_test.go是我们准备建覆盖率而新建的文件。



main.c内容如下:



test_main.c内容如下,实现了一个TestSystem的函数,里面调用了main()函数。



执行指令生成go.mod,我们生成了名为example的module。



此时的项目结构如下



接下来对使用go test建立代码覆盖率,输出文件指定为coverage.out



从上面的输出可以看出,这次代码测试的覆盖率为63.6%,耗时0.287s。


此时当前目录生成了新文件coverage.out,打开看一下里面记录了什么



coverage.out的可读性比较差,我们很难从其内容分析出覆盖率,因此可以使用go tool生成html,以可视化界面呈现代码覆盖率情况。



指令执行后会自动从终端跳转到网页打开,内容呈现如下:



红色表示需要跑到的代码但是还没跑到(如else 分支),绿色代表这一行代码已经被跑到了(v == 1分支),灰色表示这些代码无需关注(结构体定义,import等),不属于覆盖率范围内,可以忽略。


带子目录的项目建覆盖率


上面的例子只含有一个main.go的文件,实际的项目不可能只有一个main文件。一个比较完整的项目中会含有多个子目录,代表各种业务功能模块。大多时候我们都是对于某个子目录下进行修改,因此我们更关注如何对指定目录下的代码进行覆盖率检查。


我们的项目组织是这样的,main.go在项目跟目录下,gotest是我们这次新增的代码,我们需要关注这些新加的代码,而main.go作为一直存在的框架代码,无需再跑覆盖率了。



项目代码如下,main.go中或根据一定条件选择调用gotest.Add还是gotest.Division。


main.go



gotest/add.go



gotest/divide.go



而main_test.go我们无需修改,跟第一个例子的一样。


-coverpkg example/gotest



执行后生成二进制文件example,启动时带上参数,指定输出覆盖率文件coverage.cov



使用go tool渲染覆盖率文件,覆盖率以html的形式呈现



可以看到,gotest的两个文件都生成了覆盖率,add.go跑了66.7%的覆盖率,divison.go跑了0%覆盖率。这是因为main.go中只调用了add.go,divide.go还没调用。





阻塞类函数建立覆盖率


如果你的main.go是一直阻塞或者循环等待,如果想建立覆盖率那必须保证main正常退出,ctrl-c kill掉进程是不会输出覆盖率文件的,对于这种模式的主函数,需要保证优雅退出。比如ctrl-c的例子,我们需要捕捉这个信号,然后再让main正常退出,此时的覆盖率才会正常生成,例子如下:



输出


此时ctrl-c kill掉进程一样有覆盖率文件输出,使用go tool cover也可以正常打开。