golang源码分析:单测和集成测试覆盖率实现原理(1)

2023-03-01 16:20:31 浏览数 (1)

在了解集成测试覆盖率实现原理之前,先看看单测覆盖率是如何实现的:go语言采用的是插桩源码的形式,而不是待二进制执行时再去设置breakpoints。这就导致了当前go的测试覆盖率收集技术,一定是侵入式的,会修改目标程序源码。对于下面一段代码

代码语言:javascript复制
package main

import "fmt"

func main() {
  test2(3)
  fmt.Println("main")
  test2(-3)
}

func test1() {
  fmt.Println("hello")
  fmt.Println("test1")
}

func test2(a int) {
  if a > 0 {
    fmt.Println(a)
    test1()
  } else {
    fmt.Println("world")
  }
  fmt.Println("test2")
}

我们运行命令

代码语言:javascript复制
 go tool cover -mode=count -var=CoverageVariableName learn/cover/exp1/main.go >> learn/cover/exp1/main_gen.go

可以看到生成的代码如下

代码语言:javascript复制
//line learn/cover/exp2/main.go:1
package main

import "fmt"

  
func main() {CoverageVariableName.Count[0]  ;
  test2(3)
  fmt.Println("main")
  test2(-3)
}

  
func test1() {CoverageVariableName.Count[1]  ;
  fmt.Println("hello")
  fmt.Println("test1")
}

  
func test2(
    a int) {CoverageVariableName.Cont[2]  ;
  if a > 0 {CoverageVariableName.Count[4]  ;
    fmt.Println(a)
    test1 (
    CoverageVariableName.Count[5]  
    
    } else{ CoverageVariableName.Count[5]  ;{
      
  fmt.Println("world")
  }}
  
  CoverageVariableName.Count[3]  ;fmt.Println("test2")
}

var CorageVariableName = struct {
  Cou     [6]uint32
  Pos     [3 * 6]uint32
  umStmt   [6]uint16
} {
  Pos: [3 * 6]uint32{
    5, 9, 0x2000d, // [0]
    11, 14, 0x2000e, // [1]
    16, 17, 0xb0013, // [2]
    23, 23, 0x160002, // [3]
    17, 20, 0x3000b, // [4]
    20, 22, 0x30008, // [5]
  },
  NumStmt: [6]uint16{
    3, // 0
    2, // 1
    1, // 2
    1, // 3
    2, // 4
    1, // 5
  },
}

可以看到,生成了一个结构体,它有三个元素,每个元素都是一个数组。在源码的每一个逻辑分支开始的地方,都会对计数Count进行累加。

CoverageVariableName变量,其有三个比较关键的属性:

  1. `Count` uint32数组,数组中每个元素代表相应基本块(basic block)被执行到的次数
  2. `Pos` 代表的各个基本块在源码文件中的位置,三个为一组。比如这里的`21`代表该基本块的起始行数,`23`代表结束行数,`0x2000d`比较有趣,其前16位代表结束列数,后16位代表起始列数。通过行和列能唯一确定一个点,而通过起始点和结束点,就能精确表达某基本块在源码文件中的物理范围
  3. `NumStmt` 代表相应基本块范围内有多少语句(statement)

可以看到,通过打点和运行时计数累加的方式就可以知道了每一个代码块是不是被执行了,执行了多少次。依托这个go官方的工具链就可以知道,哪些代码被执行了,执行了多少次。

对于集成测试,go官方并没有给出很好用的工具,但是基于上述原理,我们很容易想到,我们可不可以用go tool cover工具在我们原有的代码上插入上述桩代码,然后,部署有桩代码的版本,集成测试的时候,我们累计代码被执行的次数,就可以很好分析集成测试的覆盖率了。

https://github.com/qiniu/goc正是基于上述原理实现的集成测试覆盖率分析工具。如何使用呢,首先安装:

代码语言:javascript复制
curl -s https://api.github.com/repos/qiniu/goc/releases/latest | grep "browser_download_url.*-darwin-amd64.tar.gz" | cut -d : -f 2,3 | tr -d " | xargs -n 1 curl -L | tar -zx && chmod  x goc && mv goc /usr/local/bin

然后我们下载官方的制定的样例包

代码语言:javascript复制
git submodule add https://github.com/enricofoltran/simple-go-server

使用goc启动一个服务注册中心,后面就可以到这个注册中心查询覆盖率

代码语言:javascript复制
1,use goc server to start a service registry center:
 ✗ goc server

第二步就是用goc工具编译生成带桩的二进制程序,然后启动我们的程序提供服务

代码语言:javascript复制
 2,use goc build to build the target service, and run the generated binary.
 ✗ goc build .
  ✗ ./simple-go-server  

第三步就是查询我们的代码覆盖率结果

代码语言:javascript复制
 3,use goc profile to get the code coverage profile of the started simple server above
 ✗ goc profile

0 人点赞