脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - Golang单元测试与覆盖率的实例讲解

Golang单元测试与覆盖率的实例讲解

2021-02-02 01:03pirlo-san Golang

这篇文章主要介绍了Golang单元测试与覆盖率的实例讲解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

1 概述

 

C/C++和Java(以及大多数的主流编程语言)都有自己成熟的单元测试框架,前者如Check,后者如JUnit,但这些编程框架本质上仍是第三方产品,为了执行单元测试,我们不得不从头开始搭建测试工程,并且需要依赖于第三方工具才能生成单元测试的覆盖率

相比之下,Go语言官方则提供了语言级的单元测试支持,即testing包,而且仅通过go工具本身就可以方便地生成覆盖率数据,也就是说,单元测试是Go语言的自带属性,除了好好设计自己的单元测试用例外,开发者不需要操心工程搭建的任何细节。没错,Golang就是这么任性。

2 单元测试

 

下面我们以《The Go Programming Language》6.5节的比特容器为例,介绍如何通过testing包和go工具集进行单元测试。

2.1 工程目录

不是说好的,Go语言单元测试不需要搭建测试工程么?其实,Golang的测试工程只有一句话:对file.go新建file_test.go文件,并在其中编写测试用例。所以,我们所谓的工程目录其实就是:

$ go env | grep GOPATH

GOPATH="/home/pirlo/go"

$ tree /home/pirlo/go/src/github.com/pirlo-san/let-us-go

/home/pirlo/go/src/github.com/pirlo-san/let-us-go

├── bitvector
│ ├── bitvector.go
│ └── bitvector_test.go
├── LICENSE
└── README.md

/home/pirlo/go是我的GOPATH,其中的github.com/pirlo-san/let-us-go是一个git工程,bitvector则是这个工程下的一个子模块,即比特容器模块,bitvector.go是模块的实现文件,bitvector_test.go则是用于测试比特容器的文件。

2.2 比特容器的实现

Golang没有容器类型,多数容器都是通过map[type]bool实现的,但是通过map实现在某些场景下比较浪费内存,比如容器元素都是一些很小的非负整数的场景:0~31,其实,我们只需要一个uint32类型4个字节就可以了,但是如果采用map[uint32]bool实现,则对每个元素都需要一个uint32的key和bool类型的value。在C/C++语言内,可以很容易地通过位域的方式达到节省内存的目的,那么Golang可不可以采用类似的方式实现呢?当然可以喽。

2.2.1 定义

?
1
2
3
4
5
6
7
type IntSet struct {
 words []uint
}
 
const (
 wordBitCount = (32 << (^uint(0) >> 63))
)

IntSet是我们定义的比特容器类型,是一个结构体,其中唯一的成员是一个uint类型的切片,想象切片的元素被有序排列成一个“比特”数组,如果容器内存在元素N,则这个数组的第N个元素的值就为1,否则就是0.

wordBitCount用于计算uint类型占用的比特数,这个数字在不同的操作系统或CPU上是不同的。

2.2.2 向容器内添加一个元素

?
1
2
3
4
5
6
7
8
9
10
11
12
// add x into set s
func (s *IntSet) Add(x int) {
 word, index := wordIndex(x)
 for word >= len(s.words) {
  s.words = append(s.words, 0)
 }
 s.words[word] |= (1 << index)
}
 
func wordIndex(x int) (int, uint) {
 return x / wordBitCount, uint(x) % wordBitCount
}

先获取这个元素在第几个“word”,以及在这个word内的第几个比特,如果words切片长度不够,则一直添加到可以包含待插入的元素为止,最后将对应元素位置的“比特位”设置为1.

2.2.3 判断某元素是否在容器内

?
1
2
3
4
5
6
7
8
9
// check wether x is in set s
func (s *IntSet) Has(x int) bool {
 word, index := wordIndex(x)
 if word >= len(s.words) {
  return false
 }
 
 return (s.words[word] & (1 << index)) != 0
}

《The Go Programming Language》内还实现了其它接口,包括String,UnionWith等,完整代码见文末链接。

2.3 单元测试用例

好了,为了测试这个比特容器模块,我们只需要在package目录内定义相应的test文件,并编写用例即可。本例即为bitvector_test.go:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package bitvector
 
import (
 "testing"
)
 
func TestAdd(t *testing.T) {
 var s IntSet
 s.Add(1)
 s.Add(2)
 s.Add(3)
 s.Add(4)
 
 if s.Has(1) == false || s.Has(2) == false || s.Has(3) == false || s.Has(4) == false {
  t.Error("Failed")
 }
 
 if s.Has(0) == true || s.Has(5) == true || s.Has(100) == true {
  t.Error("Failed")
 }
}

包声明:测试文件也归属于bitvector包,这样测试文件就可以随意访问这个包已导出和未导出的类型、函数、方法等;你可以定义成不同的包,比如package bitvector_test,这样,bitvector包对bitvector_test包来说就是一个外部库,test包只能访问其中已导出的类型、函数、方法等,这个叫做外部测试;

导入testing包:testing包拥有执行Golang单元测试所需要的一切;

编写测试函数:所有测试函数都以Test开头,入参是testing.T类型的指针,在函数内调用被测函数,并对不符合预期的结果调用类似Error、Fatal的函数,其中前者在被调用后会打印出错信息,并继续执行后续用例,而后者则在打印信息后立即终止测试,一般仅在测试出现严重问题,无法继续进行后续用例测试时才需要调用类似Fatal的接口。

2.4 执行单元测试

Golang执行单元测试的命令是go test,如果你在待测package所在的目录,则直接执行go test即可:

?
1
2
3
4
5
$ pwd
/home/pirlo/go/src/github.com/pirlo-san/let-us-go/bitvector
$ go test
PASS
ok  github.com/pirlo-san/let-us-go/bitvector 0.004s

不带任何参数的情况下,test仅输出最终的测试结果,如果要看到测试过程,可以指定-v参数:

?
1
2
3
4
5
$ go test -v
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok  github.com/pirlo-san/let-us-go/bitvector 0.004s

每个用例的执行成功与否,以及执行用时都会显示出来。

如果不在当前目录,则需要指定待测模块路径:

?
1
2
3
4
5
6
7
$ pwd
/home/pirlo/go
$ go test -v github.com/pirlo-san/let-us-go/bitvector/
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok  github.com/pirlo-san/let-us-go/bitvector 0.004s

甚至,你还可以执行所有模块的测试,方式是以三个点替代具体的模块路径:

$ go test -v ...

3 覆盖率生成

 

Golang单元测试覆盖率的生成也简单到令人发指。两步:

执行go test时指定-coverprofile参数收集覆盖率数据;

执行go tool cover生成文本、html等可视化格式的覆盖率报告。

3.1 收集覆盖率数据

?
1
2
3
4
5
6
7
8
$ go test -v -coverprofile=cover.out github.com/pirlo-san/let-us-go/bitvector/
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
PASS
coverage: 36.0% of statements
ok  github.com/pirlo-san/let-us-go/bitvector 0.009s
$ ll cover.out
-rw-rw-r-- 1 pirlo pirlo 1330 Jan 12 23:11 cover.out

3.2 生成html格式的覆盖率报告

?
1
2
3
$ go tool cover -html=cover.out -o coverage.html
$ ll coverage.html
-rw-rw-r-- 1 pirlo pirlo 4504 Jan 12 23:15 coverage.html

生成的覆盖率报告效果如下:

Golang单元测试与覆盖率的实例讲解

其中第一行左侧的下拉列表列举了所有文件的覆盖率百分比,正文则以蓝绿色字体标识已覆盖的代码行(本例的Add和Has都已经被测试过了),以红色字体标识未被覆盖的代码行(UnionWith还没有对应的测试用例),灰色字体则是类似类型定义、函数声明等不需要被跟踪的代码行。

4 小结

 

Golang的单元测试和覆盖率报告生成,过程非常简单迅捷,而且不需要借助任何第三方工具或库,除了本文所述的基本测试场景外,Golang还支持Benchmark测试、内部函数/方法打桩等,有空再聊。

本文完整代码在:这里

补充知识:GoLang Test 显示输出

默认运行 go test 不会输出 testing.T.Log() 的内容。

要显示这些内容,需要加上开关 -v

go test -v -timeout 30s xxx/xxx/package -run ^TestXXXFunction$

在 Visual Studio Code IDE 环境中,可以设置 Workspace Settings。打开 .vscode/settings.json,添加:

"go.testFlags": ["-v"],

这样,在 IDE 编辑器中,点击函数上方的 run test,自动运行 go test,会被加上 -v 标志,在 OUTPUT 窗口就可以看到 t.Logf("xxx%s","xxx") 的输出内容了。

未加设置前:

Golang单元测试与覆盖率的实例讲解

添加设置后:

Golang单元测试与覆盖率的实例讲解

以上这篇Golang单元测试与覆盖率的实例讲解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/m0_37554486/article/details/78917471

延伸 · 阅读

精彩推荐
  • GolangGolang中Bit数组的实现方式

    Golang中Bit数组的实现方式

    这篇文章主要介绍了Golang中Bit数组的实现方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    天易独尊11682021-06-09
  • Golanggo日志系统logrus显示文件和行号的操作

    go日志系统logrus显示文件和行号的操作

    这篇文章主要介绍了go日志系统logrus显示文件和行号的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    SmallQinYan12302021-02-02
  • Golanggo语言制作端口扫描器

    go语言制作端口扫描器

    本文给大家分享的是使用go语言编写的TCP端口扫描器,可以选择IP范围,扫描的端口,以及多线程,有需要的小伙伴可以参考下。 ...

    脚本之家3642020-04-25
  • Golanggolang如何使用struct的tag属性的详细介绍

    golang如何使用struct的tag属性的详细介绍

    这篇文章主要介绍了golang如何使用struct的tag属性的详细介绍,从例子说起,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看...

    Go语言中文网11352020-05-21
  • Golanggolang的httpserver优雅重启方法详解

    golang的httpserver优雅重启方法详解

    这篇文章主要给大家介绍了关于golang的httpserver优雅重启的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,...

    helight2992020-05-14
  • GolangGolang通脉之数据类型详情

    Golang通脉之数据类型详情

    这篇文章主要介绍了Golang通脉之数据类型,在编程语言中标识符就是定义的具有某种意义的词,比如变量名、常量名、函数名等等,Go语言中标识符允许由...

    4272021-11-24
  • Golanggolang json.Marshal 特殊html字符被转义的解决方法

    golang json.Marshal 特殊html字符被转义的解决方法

    今天小编就为大家分享一篇golang json.Marshal 特殊html字符被转义的解决方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧 ...

    李浩的life12792020-05-27
  • Golanggolang 通过ssh代理连接mysql的操作

    golang 通过ssh代理连接mysql的操作

    这篇文章主要介绍了golang 通过ssh代理连接mysql的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    a165861639710342021-03-08