上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
12.1 单元测试
为测试非导出成员,测试文件也放在目标包内。
- 测试文件以
_test.go
结尾。
- 通常与测试目标主文件名相同,如
sort_test.go
。 - 构建命令(
go build
)忽略测试文件。
- 测试命令(
go test
):
- 忽略以
_
或.
开头的文件。 - 忽略
testdata
子目录。 - 执行
go vet
检查。
- 测试函数名
Test<Name>
。
Test
为识别标记。<Name>
为测试名称,首字母大写。如:TestSort
。
- 测试函数内以
Error
、Fail
等方法标记失败。
Fail
:失败,继续当前函数。FailNow
: 失败,终止当前函数。SkipNow
: 跳过,终止当前函数。Log
: 输出信息,仅失败或-v
时有效。Error
:Fail + Log
。Fatal
:FailNow + Log
。Skip
:SkipNow + Log
。
os.Exit
:失败,测试进程终止。
// main_test.go package main import ( "testing" ) func TestAdd(t *testing.T) { z := add(1, 2) if z != 3 { t.FailNow() } }
$ go test -v === RUN TestAdd --- PASS: TestAdd (0.00s) PASS ok test 0.004s
模式
- 本地模式(local directory mode):
go test
,go test -v
- 不缓存测试结果。
- 列表模式(package list mode):
go test math
,go test .
,go test ./...
- 缓存结果,直接输出。避免不必要的重复运行。
- 缓存输出有
(cached)
标记。 - 某些参数(
-count
)导致缓存失效。 - 执行
go clean -testcache
清除缓存。
$ go test . ok test (cached) $ go test -count 2 . ok test 0.004s
执行
$ go test // 测试当前包。 $ go test math // 测试指定包。 $ go test ./mylib // 使用相对路径。 $ go test ./... // 测试当前及所有子包。
执行参数:
$ go help test $ go help testflag
-args
: 命令行参数。(包列表必须在此参数前)-c
: 仅编译,不执行。(用-o
修改默认测试可执行文件名)-json
: 输出 JSON 格式。-count
: 执行次数。(默认 1)-list
: 用正则表达式列出测试函数名,不执行。-run
:用正则表达式执行要执行的测试函数。-skip
: 需要跳过的测试函数。-timeout
: 超时 panic!(默认 10m)-v
: 输出详细信息。-x
: 输出构建信息。
参数可添加
test.
前缀,比如-test.v
,以便区分 benchmark 参数。
$ go test -v -run "Add" -count 2 -timeout 1m20s ./... === RUN TestAdd add_test.go:10: abc --- PASS: TestAdd (0.00s) === RUN TestAdd add_test.go:10: abc --- PASS: TestAdd (0.00s) PASS ok test/mylib 0.003s
并行
默认情况下,包内串行,多包并行。
// main_test.go func TestA(t *testing.T) { time.Sleep(time.Second * 10) } func TestB(t *testing.T) { time.Sleep(time.Second * 10) }
// ./mylib/demo_test.go func TestX(t *testing.T) { time.Sleep(time.Second * 10) }
# 单个包,串行。 $ go clean -cache -testcache $ time go test -v === RUN TestA --- PASS: TestA (10.01s) === RUN TestB --- PASS: TestB (10.01s) PASS ok test 20.022s real 0m21.960s <------ !!!! user 0m1.917s sys 0m1.381s
# 多个包,并行。 $ go clean -cache -testcache; $ time go test -v -run "Test[AX]" ./... === RUN TestA --- PASS: TestA (10.00s) PASS ok test 10.006s === RUN TestX --- PASS: TestX (10.00s) PASS ok test/mylib 10.006s real 0m11.956s <------- !!!! user 0m1.915s sys 0m1.587s
参数 -p, -parallel
用于设置 GOMAXPROCS
,这会影响并发执行。
$ go clean -cache -testcache $ time go test -v -run "Test[AX]" -p 1 ./... === RUN TestA --- PASS: TestA (10.00s) PASS ok test 10.023s === RUN TestX --- PASS: TestX (10.00s) PASS ok test/mylib 10.008s real 0m23.227s <------ !!!! user 0m2.371s sys 0m2.082s
调用 t.Parallel()
后,并发测试启动后暂停( PAUSE
)。
等所有串行测试结束后,并发再恢复( CONT
, continue)执行。
func TestA(t *testing.T) { t.Parallel() // 通常放在第一行。 time.Sleep(time.Second * 10) } func TestB(t *testing.T) { t.Parallel() time.Sleep(time.Second * 10) } func TestC(t *testing.T) { time.Sleep(time.Second * 10) }
# AB 并行。 $ go clean -cache -testcache; go test -v -run "[AB]" === RUN TestA === PAUSE TestA === RUN TestB === PAUSE TestB === CONT TestA === CONT TestB --- PASS: TestB (10.00s) --- PASS: TestA (10.00s) PASS ok test 10.011s
# AB 并行,C 串行。 $ go clean -cache -testcache; go test -v === RUN TestA === PAUSE TestA === RUN TestB === PAUSE TestB === RUN TestC --- PASS: TestC (10.01s) // 等待串行结束。 === CONT TestA // A 恢复。(并发) === CONT TestB // B 恢复。(并发) --- PASS: TestB (10.01s) --- PASS: TestA (10.01s) PASS ok test 20.029s
如设置 -cpu
、 -count
参数,那么同一测试函数依旧串行执行多次。
$ go clean -cache -testcache; go test -v -count 2 -run "TestA" === RUN TestA === PAUSE TestA === CONT TestA --- PASS: TestA (10.01s) === RUN TestA === PAUSE TestA === CONT TestA --- PASS: TestA (10.00s) PASS ok test 20.017s
内部实现
每个测试函数都在独立 goroutine 内运行。
正常情况下,执行器会阻塞,等待 test goroutine 结束。
// src/testing/testing.go func runTests(...) { for _, test := range tests { t.Run(test.Name, test.F) // 串行。 } } func (t *T) Run(...) { go tRunner(t, f) if !<-t.signal { runtime.Goexit() } } func tRunner(t *T, fn func(t *T)) { signal := true defer func() { t.signal <- signal }() defer func() { t.runCleanup() }() fn(t) }
调用 t.Parallel
,该方法会立即发回信号,让外部阻塞( Run
)结束,继续下一个测试。
自身阻塞,等待串行测试结束后发回信息,恢复执行。
func (t *T) Parallel() { t.chatty.Updatef(t.name, "=== PAUSE %s\n", t.name) t.signal <- true // Release calling test. t.context.waitParallel() t.chatty.Updatef(t.name, "=== CONT %s\n", t.name) }
助手
将调用 Helper
的函数标记为测试助手。
输出测试信息时跳过助手函数,直接显示测试函数文件名、行号。
- 直接在测试函数中调用无效。
- 测试助手可用作断言。
package main import ( "testing" ) func assert(t *testing.T, b bool) { t.Helper() if !b { t.Fatal("assert fatal") } } func TestA(t *testing.T) { assert(t, false) }
$ go test -v -run "A" === RUN TestA main_test.go:13: assert fatal # 不会显示助手函数信息。 --- FAIL: TestA (0.00s) FAIL exit status 1 FAIL test 0.011s
清理
为测试函数注册清理函数,在测试结束时执行。
- 如注册多个,则按 FILO 顺序执行。
- 即便发生
panic
,也能确保清理函数执行。
func TestA(t *testing.T) { t.Cleanup(func(){ println("1 cleanup.") }); t.Cleanup(func(){ println("2 cleanup.") }); t.Cleanup(func(){ println("3 cleanup.") }); t.Log("body.") }
$ go test -v -run "A" === RUN TestA body. 3 cleanup. 2 cleanup. 1 cleanup. --- PASS: TestA (0.00s) PASS ok test 0.004s
和 defer
的区别:即便在其他函数内注册,也会等测试结束后再执行。
func TestA(t *testing.T) { func() { t.Cleanup(func(){ println("cleanup.") }); }() func() { defer println("defer.") }() t.Log("body.") }
$ go test -v -run "A" === RUN TestA defer. body. cleanup. --- PASS: TestA (0.00s) PASS ok test 0.008s
可用来写 Helper
函数。
func newDatabase(t *testing.T) *DB { t.Helper() d := Database.Open() t.Cleanup(func(){ d.close() }) return &d } func TestSelect(t *testing.T) { db = newDatabase(t) ... }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论