编写 Golang 单元测试(Unit Test)
单元测试文件和用例方法命名
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