单元测试文件和用例方法命名

Go 语言推荐测试文件和源代码文件放在一起。测试文件以 “_test.go” 结尾(这样在 go build 的时候才不会被包含)。

example/
    |-- biz.go
    |-- biz_test.go

普通测试 TestXxx

普通测试用例的方法以 Test 开头,参数为 t *testing.T

func TestXxx(t *testing.T){
    err := biz()
    if err != nil{
        t.Errorln(err) // t.Errorln 会标记测试失败
    }
    t.Infoln("ok") // 测试成功
}

基准测试 BenchmarkXxx

基准测试的方法以 Benchmark 开头,参数是 testing.B

func BenchmarkXxx(b *testing.B) {
    for i := 0; i < b.N; i++ {
        fmt.Sprintf("hello")
    }
}

基准测试函数必须运行 b.N 次目标代码,b.N 会动态调整,知道基准测试功能持续足够长时间以可靠的计时为止。

基准测试的输出类似

BenchmarkHello    10000000    282 ns/op

表示该基准测试以平均 282 ns 每次的速度运行了 10000000 次。

如果基准测试前面会有一些初始化操作,需要调用一下 b.ResetTimer() 让时间计算更准确。

func BenchmarkBiz(b *testing.B) {
    Init()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        biz()
    }
}

如果基准测试需要并发,则要使用 b.RunParallel 函数。

func BenchmarkTemplateParallel(b *testing.B) {
    templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
    b.RunParallel(func(pb *testing.PB) {
        var buf bytes.Buffer
        for pb.Next() {
            buf.Reset()
            templ.Execute(&buf, "World")
        }
    })
}

示例测试 ExampleXxx

测试函数以 Example 开头,没有测试参数。然后函数内部以 // Output: 开头下面的注释和标准输出比较(忽略前后空格)。

func ExampleSalutations() {
    fmt.Println("hello, and")
    fmt.Println("goodbye")
    // Output:
    // hello, and
    // goodbye
}

// 如果输出是无序的
func ExamplePerm() {
    for _, value := range Perm(4) {
        fmt.Println(value)
    }
    // Unordered output: 4
    // 2
    // 1
    // 3
    // 0
}

使用 Example 的时候有一些函数命名约定:函数 F,类型 T,类型 T 上面定义的方法 M。

func Example() { ... }
func ExampleF() { ... }
func ExampleT() { ... }
func ExampleT_M() { ... }

子测试 t.Run

在一个测试中可以通过 t.Run 创建不同的子测试。testing.T 和 testing.B 都支持子测试。

func TestAdd(t *testing.T) {
    t.Run("pos", func(t *testing.T) {
        if Add(1, 3) != 4 {
                t.Fatal("fail")
        }
    })
    t.Run("neg", func(t *testing.T) {
        if Add(2, -3) != -1 {
                t.Fatal("fail")
        }
    })
}

普通的测试失败使用 t.Error/t.Errorf,这里使用的是 t.Fatal/t.Fatalf,区别在于前者遇错不停,会继续执行其他的测试用例,后者与错即停。

主测试 TestMain

有些测试场景下需要做很多的初始化操作,测试完了也需要做清理操作。一般会将初始化和清理的方法实现后在测试的方法里调用。这种场景也可以使用 TestMain 方法。

如果测试文件中包含函数 TestMain,那么生成的测试将调用 TestMain(m),而不是直接运行测试。

在 TestMain 中调用 m.Run() 触发所有测试用例的执行,并使用 os.Exit() 获取执行返回的错误码,如果不为 0 则说明用例失败。

package hello

import(
    "fmt"
    "testing"
)

func TestAdd(t *testing.T) {
    r := Add(1, 2)
    if r !=3 {
        t.Errorf("Add(1, 2) failed. Got %d, expected 3.", r)
    }
}

func TestMain(m *testing.M) {
    fmt.Println("begin")
    code := m.Run()
    fmt.Println("end")
    os.Exit(code)
}

帮助函数

对于测试中的一些重复逻辑,可以抽取出来作为公共的帮助函数(helpers)以增加测试代码的可读性和维护性。

在 Go 1.9 中添加了 Helper 方法,testing.T 和 testing.B 中都添加了该方法。该方法可以标记某个测试方法是一个 helper 函数,但一个测试包在输出测试的文件和行号信息时,将会输出helper 函数的调用者的信息,而不是输出 helper 函数的内部信息。

package p

import "testing"

func failure(t *testing.T) {
    t.Helper() // This call silences this function in error reports.
    t.Fatal("failure")
}

func Test(t *testing.T) {
    failure(t)
}

这里 failure 函数被标记为 helper 函数,所以 t.Fatal 被调用时,错误信息将会定位输出在 11 行,而不是第 7 行。

参考

https://studygolang.com/articles/10752

https://chyroc.cn/posts/go-test-introduction/

https://geektutu.com/post/quick-go-test.html