1.go test之测试函数

在包目录内,所有以_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:\workspace\GoRepo\gopl\ch11\word>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:\workspace\GoRepo\gopl\ch11\word1>go test
PASS
ok      _/D_/workspace/GoRepo/gopl/ch11/word1   0.955s

如果显示PASS,说明所有测试用例均已经通过。

参数-v可用于打印每个测试函数的名字和运行时间:

D:\workspace\GoRepo\gopl\ch11\word1>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:\workspace\GoRepo\gopl\ch11\word1>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)
        }
    }
}