Golang 单元测试规范
Go 单元测试概要
go test
_test.gogo testgo build
Go 单元测试的基本规范
Go 单元测试的基本规范如下:
func TestName(t *testing.T)*testing.Tfunc TestName(t *testing.T)func Test_name(t *testing.T)func Testname(t *testing.T){source_filename}_test.go
*_test.go
从一个简单测试用例来确认 go test 的各种使用方法
一个简单的 xxx_test.go 的单元测试文件如下,里面有两个测试方法:
package util
import (
"testing"
)
func TestSum(t *testing.T) {
if Sum(1, 2, 3) != 6 {
t.Fatal("sum error")
}
}
func TestAbs(t *testing.T) {
if Abs(5) != 5 {
t.Fatal("abs error, except:5, result:", Abs(5))
}
}
go test -v 执行单测并打印详情
运行方法:进入到包内,运行命令 go test -v ,参数 -v 可以打印详情。 也可以只运行某个方法的单元测试: go test -v -run="xxx" ,支持正则表达式。
allen@MackBook:~/work/goDev/Applications/src/baseCodeExample/gotest$go test -v
=== RUN TestSum
--- PASS: TestSum (0.00s)
=== RUN TestAbs
--- PASS: TestAbs (0.00s)
PASS
ok baseCodeExample/gotest 0.005s
allen@MackBook:~/work/goDev/Applications/src/baseCodeExample/gotest$go test -v -run="Abs"
=== RUN TestAbs
--- PASS: TestAbs (0.00s)
PASS
ok baseCodeExample/gotest 0.006s
go test -v -cover 执行单测并计算覆盖率
go test 工具还有个功能是测试单元测试的覆盖率,用法为 go test -v -cover, 示例如下:
allen@MackBook:~/work/goDev/Applications/src/baseCodeExample/gotest$go test -v -cover
=== RUN TestSum
--- PASS: TestSum (0.00s)
=== RUN TestAbs
--- PASS: TestAbs (0.00s)
PASS
coverage: 85.7% of statements
ok baseCodeExample/gotest 0.005s
从覆盖率来看(coverage: 85.7% of statements),单元测试没有覆盖全部的代码,只有 85.7% ,我们可以通过如下命令将 cover 的详细信息保存到cover.out 中。
go test -cover -coverprofile=cover.out -covermode=count
注:
-cover 允许代码分析
-covermode 代码分析模式(set:是否执行;count:执行次数;atomic:次数,并发执行)
-coverprofile 输出结果文件
然后再通过
go tool cover -func=cover.out
查看每个方法的覆盖率。
allen@MackBook:~/work/goDev/Applications/src/baseCodeExample/gotest$go tool cover -func=cover.out
baseCodeExample/gotest/compute.go:5: Sum 100.0%
baseCodeExample/gotest/compute.go:13: Abs 66.7%
total: (statements) 85.7%
这里发现是 Abs 方法没有覆盖完全,因为我们的用例只用到了正数的那个分支。 还可以使用 html 的方式查看具体的覆盖情况。
go tool cover -html=cover.out
会默认打开浏览器,将覆盖情况显示到页面中:

可以看出 Abs 方法的负数分支没有覆盖到。将 TestAbs 方法修改如下即可:
func TestAbs(t *testing.T) {
if Abs(5) != 5 {
t.Fatal("abs error, except:5, result:", Abs(5))
}
if Abs(-4) != 4 {
t.Fatal("abs error, except:4, result:", Abs(-4))
}
}
再次运行:
go test -cover -coverprofile=cover2.out -covermode=count
go tool cover -func=cover2.out
运行结果如下:
allen@MackBook:~/work/goDev/Applications/src/baseCodeExample/gotest$go test -cover -coverprofile=cover2.out -covermode=count
PASS
coverage: 100.0% of statements
ok baseCodeExample/gotest 0.006s
allen@MackBook:~/work/goDev/Applications/src/baseCodeExample/gotest$go tool cover -func=cover2.out
baseCodeExample/gotest/compute.go:5: Sum 100.0%
baseCodeExample/gotest/compute.go:13: Abs 100.0%
total: (statements) 100.0%
这个说明已经达到了 100% 的覆盖率了。
Go 单测覆盖度的相关命令汇总如下:
go test -v -cover
go test -cover -coverprofile=cover.out -covermode=count
go tool cover -func=cover.out
Go 单测常见使用方法
测试单个文件
通常,一个包里面会有多个方法,多个文件,因此也有多个 test 用例,假如我们只想测试某一个方法的时候,那么我们需要指定某个文件的某个方法
如下:
allen@MackBook:~/work/goDev/Applications/src/gitlab.allen.com/avatar/app_server/service/centralhub$tree .
.
├── msghub.go
├── msghub_test.go
├── pushhub.go
├── rtvhub.go
├── rtvhub_test.go
├── userhub.go
└── userhub_test.go
0 directories, 7 files
总共有7个文件,其中有三个test文件,如果直接运行 go test,就会测试所有test.go文件了。
但是,假如我们只更新了 rtvhub.go 里面的代码,所以我只想要测试 rtvhub.go 里面的某个方法,那么就需要指定文件,具体的方法就是同时指定我们需要测试的test.go 文件和 它的源文件,如下:
go test -v msghub.go msghub_test.go
测试单个文件下的单个方法
在测试单个文件之下,假如我们单个文件下,有多个方法,我们还想只是测试单个文件下的单个方法,要如何实现?我们需要再在此基础上,用 -run 参数指定具体方法或者使用正则表达式。
假如 test 文件如下:
package centralhub
import (
"context"
"testing"
)
func TestSendTimerInviteToServer(t *testing.T) {
ctx := context.Background()
err := sendTimerInviteToServer(ctx, 1461410596, 1561445452, 2)
if err != nil {
t.Errorf("send to server friendship build failed. %v", err)
}
}
func TestSendTimerInvite(t *testing.T) {
ctx := context.Background()
err := sendTimerInvite(ctx, "test", 1461410596, 1561445452)
if err != nil {
t.Errorf("send timeinvite to client failed:%v", err)
}
}
只测试 TestSendTimerInvite 方法
go test -v msghub.go msghub_test.go -run TestSendTimerInvite
测试所有正则匹配 SendTimerInvite 的方法
go test -v msghub.go msghub_test.go -run "SendTimerInvite"
测试所有方法
直接 go test 就行
竞争检测(race detection)
go run -race 执行竞争检测
当两个goroutine并发访问同一个变量,且至少一个goroutine对变量进行写操作时,就会发生数据竞争(data race)。 为了协助诊断这种bug,Go提供了一个内置的数据竞争检测工具。 通过传入-race选项,go tool就可以启动竞争检测。
$ go test -race mypkg // to test the package
$ go run -race mysrc.go // to run the source file
$ go build -race mycmd // to build the command
$ go install -race mypkg // to install the package
示例代码
package main
import (
"fmt"
"time"
)
func main() {
var i int = 0
go func() {
for {
i++
fmt.Println("subroutine: i = ", i)
time.Sleep(1 * time.Second)
}
}()
for {
i++
fmt.Println("mainroutine: i = ", i)
time.Sleep(1 * time.Second)
}
}
演示结果
$ go run -race testrace.go
mainroutine: i = 1
==================
WARNING: DATA RACE
Read at 0x00c0000c2000 by goroutine 6:
main.main.func1()
/Users/wudebao/Documents/workspace/goDev/Applications/src/base-code-example/system/testrace/testrace.go:12 +0x3c
Previous write at 0x00c0000c2000 by main goroutine:
main.main()
/Users/wudebao/Documents/workspace/goDev/Applications/src/base-code-example/system/testrace/testrace.go:18 +0x9e
Goroutine 6 (running) created at:
main.main()
/Users/wudebao/Documents/workspace/goDev/Applications/src/base-code-example/system/testrace/testrace.go:10 +0x7a
==================
subroutine: i = 2
mainroutine: i = 3
subroutine: i = 4
mainroutine: i = 5
subroutine: i = 6
mainroutine: i = 7
subroutine: i = 8
subroutine: i = 9
mainroutine: i = 10
最后