go test命令是一个按照一定的约定和组织来测试代码的程序。在包目录内,所有以_test.go为后缀名的源文件在执行go build时不会被构建成包的一部分,它们是go test测试的一部分。
在*_test.go文件中,有三种类型的函数:功能测试函数、基准性能测试(benchmark)函数、示例函数。
1. test文件和函数命名
- 文件必须是*_test.go
- 功能测试函数名必须以Test开头,函数参数必须是*testing.T
- 性能测试函数名必须以Benchmark开头,函数参数必须是*testing.B
- 示例测试函数名必须以Example开头,函数参数无要求
go test命令会遍历所有的*_test.go文件中符合上述命名规则的函数,生成一个临时的main包用于调用相应的测试函数,接着构建并运行、报告测试结果,最后清理测试中生成的临时文件。
2. Go test 的测试用例形式
测试用例有四种形式:
TestXxxx(t *testing.T) // 基本测试用例
BenchmarkXxxx(b *testing.B) // 压力测试的测试用例
Example_Xxx() // 测试控制台输出的例子
TestMain(m *testing.M) // 测试Main函数
3. 编写测试文件
-
每个测试函数必须导入testing包。测试函数的名字必须以Test开头,可选的后缀名必须以大写字母开头:
-
测试函数有如下的签名:
func TestName(t *testing.T) {
// ...
}
func TestSin(t *testing.T) { /* ... */ }
func TestCos(t *testing.T) { /* ... */ }
func TestLog(t *testing.T) { /* ... */ }
- 其中t参数用于报告测试失败和附加的日志信息。让我们定义一个实例包gopl.io/ch11/word1,其中只有一个函数IsPalindrome用于检查一个字符串是否从前向后和从后向前读都是一样的。(下面这个实现对于一个字符串是否是回文字符串前后重复测试了两次;我们稍后会再讨论这个问题。)
package word
import "testing"
import "unicode"
// IsPalindrome reports whether s reads the same forward and backward.
// (Our first attempt.)
// func IsPalindrome(s string) bool {
// for i := range s {
// if s[i] != s[len(s)-1-i] {
// return false
// }
// }
// return true
// }
// Letter case is ignored, as are non-letters.
// unicode用于支持“été”中的é等非ASCII字符
func IsPalindrome(s string) bool {
var letters []rune
for _, r := range s {
if unicode.IsLetter(r) {
letters = append(letters, unicode.ToLower(r))
}
}
for i := range letters {
if letters[i] != letters[len(letters)-1-i] {
return false
}
}
return true
}
func TestPalindrome(t *testing.T) {
if !IsPalindrome("detartrated") {
t.Error(`IsPalindrome("detartrated") = false`)
}
if !IsPalindrome("kayak") {
t.Error(`IsPalindrome("kayak") = false`)
}
}
func TestNonPalindrome(t *testing.T) {
if IsPalindrome("palindrome") {
t.Error(`IsPalindrome("palindrome") = true`)
}
}
- 在相同的目录下,word_test.go测试文件中包含了TestPalindrome和TestNonPalindrome两个测试函数。每一个都是测试IsPalindrome是否给出正确的结果,并使用t.Error报告失败信息;
$ cd $GOPATH/src/gopl.io/ch11/word1
$ go test
ok gopl.io/ch11/word1 0.008s
# 或直接运行测试程序 go test $GOPATH/src/gopl.io/ch11/word1.go
# 如果有依赖,还需添加依赖文件, go test a_test.go b.go c.go
4. 测试过程
go test [ -run=regex ] 功能测试(local mode)
go test [ -run=regex ] path/to/dir 功能测试(package list mode)
go test -bench=. [ -run=regex ] path/to/dir 性能测试
go test -bench=. [ -run=regex ] -benchmem path/to/dir 性能测试,输出内存分配信息
#参数-v可用于打印每个测试函数的名字和运行时间
#参数-run对应一个正则表达式,只有单元测试函数名TestXXX被它正确匹配的测试函数才会被go test
#参数-benchmem表示输出内存信息等
#参数-bench表示采用性能测试时,正则匹配的具体函数,如果.表示运行所有Benmark函数;
#参数-count表示总体运行benchmark的次数,默认为1;
#例如如下命令将运行func BenchmarkAddEip(b *testing.B) {...}函数
$ go test -bench=AddEip -benchmem -v -count 3
$ go test --bench=. -benchmem -v
$ go test sum_test.go sum.go
-
对于功能测试,go test会并发执行,默认最大是8个并发,可以通过-parallel指定,最终按测试函数在文件中的顺序打印测试结果
-
对于性能测试,go test会顺序执行,最终按测试函数在文件中的顺序打印测试结果
5. 运行模式及Cache
-
本地目录模式,在没有包参数(例如 go test 或 go test -v )调用时发生。在此模式下, go test 编译当前目录中找到的包和测试,然后运行测试二进制文件。在这种模式下,caching 是禁用的。在包测试完成后,go test 打印一个概要行,显示测试状态、包名和运行时间。
-
包列表模式,在使用显示包参数调用 go test 时发生(例如 go test math , go test ./... 甚至是 go test . )。在此模式下,go 测试编译并测试在命令上列出的每个包。如果一个包测试通过, go test 只打印最终的 ok 总结行。如果一个包测试失败, go test 将输出完整的测试输出。如果使用 -bench 或 -v 标志,则 go test 会输出完整的输出,甚至是通过包测试,以显示所请求的基准测试结果或详细日志记录。
在本地目录模式中,无cache功能,但在包列表模式中,测试成功的测试会被cache,如果后续没有改过测试函数,也没有改过被测函数,那么将直接使用cache的结果加速整个测试
go clean -cache 清理所有cache,包括testcache
go clean -testcache 只清理testcache
6. Log
为了避免两次输入较长的字符串,我们使用了提供了有类似Printf格式化功能的 Errorf函数来汇报错误结果。
当添加了这两个测试用例之后,go test返回了测试失败的信息
-
t.Error/Errorf 测试失败,输出err log
-
t.Fatal/Fatalf 测试失败,输出err log,立即退出后续测试
测试过程中如果需要显示fmt/t.Log的输出需要指定-v参数
func TestFrenchPalindrome(t *testing.T) {
if !IsPalindrome("été") {
t.Error(`IsPalindrome("été") = false`)
}
}
func TestCanalPalindrome(t *testing.T) {
input := "A man, a plan, a canal: Panama"
if !IsPalindrome(input) {
t.Errorf(`IsPalindrome(%q) = false`, input)
}
}
7. 性能测试
命令行选项 | 含义描述 |
---|---|
-c | 生成用于运行测试的可执行文件,但不执行它。这个可执行文件会被命名为“pkg.test”,其中的“pkg”即为被测试代码包的导入路径的最后一个元素的名称。 |
-i | 安装/重新安装运行测试所需的依赖包,但不编译和运行测试代码。 |
-o | 指定用于运行测试的可执行文件的名称。追加该标记不会影响测试代码的运行,除非同时追加了标记-c或-i。 |
-v | 输出详细的测试过程 |
func BenchmarkByPointer(b *testing.B) {
var r *bar
b.ResetTimer()
for i := 0; i < b.N; i++ {
r = byPointer(&input)
}
pointerResult = r
}
var valueResult bar
func BenchmarkByValue(b *testing.B) {
var r bar
initfun()
b.StartTimer()
for i := 0; i < b.N; i++ {
r = byValue(input)
}
b.StopTimer()
valueResult = r
}
- 性能测试中循环调用的次数是用b.N控制的,测试每个函数时如果运行时间没有超过1s,那么就会调大b.N重新测试,直到达到1s,结果展示的就是此时的b.N值和平均每次耗时
- 性能测试可以使用b.StopTimer/b.StartTimer/b.RestTimer排除一些干扰代码
- 重点关注每次操作的耗时和内存分配次数
8. 编译可执行文件
#编译二进制测试文件
go test -c
#使用测试文件,添加-v参数
go test -v -o xxx.test文件
#使用二进制文件进行性能测试
./abc.test -test.bench=ShortConn -test.v -test.benchmem
./abc.test -test.bench=AddEip -test.v -test.benchmem
# 指定总循环次数
./abc.test -test.bench=. -test.v -test.benchmem -test.count 3
#指定使用内核,默认使用所有内核
./abc.test -test.bench=. -test.v -test.benchmem -test.cpu 1 2 3 4 5 6 7
参考文档:
go test: 要点:https://zhuanlan.zhihu.com/p/80567543
go test: http://shouce.jb51.net/gopl-zh/ch11/ch11-01.html
Go Test 单元测试简明教程: https://geektutu.com/post/quick-go-test.html