《南皮县志·风土志下·歌谣》:“兵马不动,粮草先行”。作战时兵马还没出动,军用粮草的运输要先行一步。在开发新功能之前,先编写测试代码,然后只编写使测试通过的功能代码,这种测试驱动开发的软件开发模式是我非常推荐的。
对 Dit 的贡献要求需要通过单元测试,编写 Dit 的任意模块,都需要一并编写测试用例。本文先简述一下 Go 对测试的支持,后续会陆续提供 Dit 的测试方案和测试报告。
Go 对测试的支持
Go 自带的测试框架 testing 支持单元测试和性能测试。Go 规定测试文件以 _test.go
为后缀,使用命令 go test
命令自动运行测试用例。
单元测试
以 Test
开头的方法为一个测试用例,并拥有一个参数 *testing.T
, 写法如下:
1
| func TestXxx(*testing.T)
|
Xxx
部分为任意的字母数字组合,首字母不能是小写字母。*testing.T
可记录错误或者标记错误状态。可通过Short判断略过一部分测试,加快测试时间。如下:
1 2 3 4 5 6
| func TestTimeConsuming(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } ... }
|
性能测试
性能测试用例以 Benchmark
开始,参数为 *testing.B
, 写法如下:
1
| func BenchmarkXxx(*testing.B)
|
使用 go test
运行性能测试用例时,需要加上参数 -bench
。
在编写性能测试用例时,需牢记在循环体内使用 testing.B.N
, 以使测试可以正常的运行:
1 2 3 4 5
| func BenchmarkHello(b *testing.B) { for i := 0; i < b.N; i++ { fmt.Sprintf("hello") } }
|
对耗时的初始化操作,可在测试用例内部重置计时器 ResetTimer
,确保引入不必要的误差:
1 2 3 4 5 6 7
| func BenchmarkBigLen(b *testing.B) { big := NewBig() b.ResetTimer() for i := 0; i < b.N; i++ { big.Len() } }
|
使用 RunParallel
测试并行模块, 运行时需要加上参数 -cpu
:
1 2 3 4 5 6 7 8 9 10
| 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") } }) }
|
标准输出测试
testing 包提供对标准终端输出的测试, 测试用例以 Example
开始,结果使用注释的方式,写在 Output:
之后:
1 2 3 4
| func ExampleHello() { fmt.Println("hello") }
|
Example 用例的命名规则:
1 2 3 4 5 6 7 8 9 10
| func Example() { ... } func ExampleF() { ... } func ExampleT() { ... } func ExampleT_M() { ... } func Example_suffix() { ... } func ExampleF_suffix() { ... } func ExampleT_suffix() { ... } func ExampleT_M_suffix() { ... }
|
Main 测试
当需要批量 setup 或 teardown 测试环境时,可使用:
1
| func TestMain(m *testing.M)
|
TestMain 简单实现如下:
1 2 3 4
| func TestMain(m *testing.M) { flag.Parse() os.Exit(m.Run()) }
|
Demo
写一个简单的示例,来 Demo 一下 testing 测试框架的用法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| package test import ( "errors" ) func div(a, b int) (int, error) { if b == 0 { return 0, errors.New("b must NOT be 0") } return a / b, nil } package test import ( "errors" "flag" "fmt" "os" "testing" "time" ) func TestDivNormal(t *testing.T) { ret, err := div(6, 2) if err != nil || ret != 3 { t.Error("6/2=3") } } func TestDivZero(t *testing.T) { _, err := div(6, 0) if err.Error() != errors.New("b must NOT be 0").Error() { t.Error("zero div error") } } func BenchmarkDiv(b *testing.B) { b.Log("run times:", b.N) for i := 0; i < b.N; i++ { div(6, 2) } } func BenchmarkDiv_Sleep(b *testing.B) { b.Log("run times:", b.N) time.Sleep(3000) b.ResetTimer() for i := 0; i < b.N; i++ { div(6, 2) } } func ExampleOutput() { ret, _ := div(6, 2) fmt.Println("6 / 2 =", ret) } func TestMain(m *testing.M) { flag.Parse() fmt.Println("Setup ... Done") ret := m.Run() fmt.Println("Teardown ... Done") os.Exit(ret) }
|
运行 go test -v
, 结果如下:
1 2 3 4 5 6 7 8 9 10 11
| localhost:test zdd$ go test -v Setup ... Done PASS Teardown ... Done ok github.com/zddhub/hellogo/test 0.005s
|
默认不执行性能测试用例,使用go test -v -bench .
执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| localhost:test zdd$ go test -v -bench . Setup ... Done === RUN TestDivNormal --- PASS: TestDivNormal (0.00s) === RUN TestDivZero --- PASS: TestDivZero (0.00s) === RUN: ExampleOutput --- PASS: ExampleOutput (0.00s) PASS BenchmarkDiv 100000000 16.2 ns/op --- BENCH: BenchmarkDiv div_test.go:27: run times: 1 div_test.go:27: run times: 100 div_test.go:27: run times: 10000 div_test.go:27: run times: 1000000 div_test.go:27: run times: 100000000 BenchmarkDiv_Sleep 100000000 16.4 ns/op --- BENCH: BenchmarkDiv_Sleep div_test.go:34: run times: 1 div_test.go:34: run times: 100 div_test.go:34: run times: 10000 div_test.go:34: run times: 1000000 div_test.go:34: run times: 100000000 Teardown ... Done ok github.com/zddhub/hellogo/test 3.305s
|
木乙言己 zddhub 出品
微信号: zddnotes
Just for fun!
文章只写给自己,如果你也喜欢,欢迎扫描以下二维码关注哦~