在包目录内,所有以_test.go
为后缀名的源文件在执行go build时不会被构建成包的一部分,它们是go test测试的一部分。
在*_test.go
文件中,有三种类型的函数:测试函数、基准测试(benchmark)函数、示例函数。一个测试函数是以Test为函数名前缀的函数,用于测试程序的一些逻辑行为是否正确;go test命令会调用这些测试函数并报告测试结果是PASS或FAIL。基准测试函数是以Benchmark为函数名前缀的函数,它们用于衡量一些函数的性能;go test命令会多次运行基准函数以计算一个平均的执行时间。示例函数是以Example为函数名前缀的函数,提供一个由编译器保证正确性的示例文档。
本节主要简单介绍一下测试函数,以及go test的一些常用命令。
现在的目录结构如下:在word包下,含有两个文件,一个是word.go,该文件内有一个判断字符串是否是回文串的函数。另一个是测试文件word_test.go。
D:workspaceGoRepogoplch11word>ls
word.go word_test.go
word.go文件
package word import "unicode" func IsPalindrome(s string) bool { // 处理非ASCII字符,比如中文 var letters []rune for _, r := range s { if unicode.IsLetter(r) { letters = append(letters, unicode.ToLower(r)) } } length := len(letters) for i := 0; i < length/2; i++ { if letters[i] != letters[length-i-1] { return false } } return true }
一个简单的测试函数可以写成如下,每个测试函数必须导入testing包。测试函数有如下的签名:
func TestName(t *testing.T) { // ... }
特别注意,测试函数的名字必须以Test开头,可选的后缀名必须以大写字母开头,比如
func TestFoo(t *testing.T) { // 正确 // ... } func Testfoo(t *testing.T) { // 错误 // ... }
一个简单的测试文件可以写成这样:
package word import "testing" func TestIsPalindrome(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("abcd") { t.Error(`IsPalindrome("abcd") = true`) } }
go test 命令如果没有参数指定具体哪个包,那么将默认采用当前目录对应的包(和go build命令一样)。我们可以用下面的命令构建和运行测试。
D:workspaceGoRepogoplch11word1>go test PASS ok _/D_/workspace/GoRepo/gopl/ch11/word1 0.955s
如果显示PASS,说明所有测试用例均已经通过。
参数-v
可用于打印每个测试函数的名字和运行时间:
D:workspaceGoRepogoplch11word1>go test -v === RUN TestIsPalindrome --- PASS: TestIsPalindrome (0.00s) === RUN TestNonPalindrome --- PASS: TestNonPalindrome (0.00s) PASS ok _/D_/workspace/GoRepo/gopl/ch11/word1 1.963s
参数-run
对应一个正则表达式,只有测试函数名被它正确匹配的测试函数才会被go test
测试命令运行:
D:workspaceGoRepogoplch11word1>go test -v -run="Non" === RUN TestNonPalindrome --- PASS: TestNonPalindrome (0.00s) PASS ok _/D_/workspace/GoRepo/gopl/ch11/word1 0.870s
此外,还有-race参数也比较常用,用于检测是否存在数据竞争非常有用,这个点在用到的时候再讲。
最后,在写测试文件的时候,推荐将所有测试数据合并到一个测试的表格中。比如,写成下面这样子。这种表格驱动的测试在Go语言中很常见。我们可以很容易地向表格添加新的测试数据,并且后面的测试逻辑也没有冗余,这样我们可以有更多的精力去完善错误信息。
func TestAllCases(t *testing.T) { // 把所有测试用例装在一个结构体数组中 var tests = []struct { input string want bool } { {"",true}, {"a",true}, {"aa",true}, {"abba",true}, {"ab",false}, {"A man, a plan, a canal: Panama",true}, {"哈哈哈",true}, } for _, test := range tests { // 如果返回结果不和预期的一致,则测试不通过 if res := IsPalindrome(test.input); res != test.want { t.Errorf("IsPalindrome(%q) = %v", test.input, res) } } }