testing包

testing包提供了自动化测试相关的框架,测试源码文件的主名称通常已被测试源码文件的名字作为开头,文件名必须以xx_test.go结尾,例如我们的被测试源码文件名称是demo.go 那么我们测试源码文件名称应该是demo_test.go

功能测试test

1测试方法样式是func Testxxx(t _testing.T),方法名词必须以Test开头,xxx首字母需要大写,func TestFoo(t _testing.T)
2测试方法参数必须 t *testing.T,函数中通过调用testing.T的Error, Errorf和FailNow, Fatal, FatalIf等方法说明测试不通过,以error 打印函数不会终止测试,Fatal类型会造成该单元测试终止。
当然通过调用Log方法用来记录测试的信息。
eg:

  1. import "testing"
  2. func TestFoo(t *testing.T) {
  3. t.Log("test")
  4. }

测试代码 calc.go

  1. package calc
  2. func Add(a, b int) int {
  3. return a + b
  4. }
  5. func Sub(a, b int) int {
  6. return a - b
  7. }
  8. func Mul(a, b int) int {
  9. return a * b
  10. }
  11. func Div(a, b int) int {
  12. return a / b
  13. }

单元测试代码

  1. func TestAdd(t *testing.T) {
  2. a := 1
  3. b := 2
  4. c := Add(a, b)
  5. if c != 3 {
  6. t.Fatalf("err:%d + %d =%d", a, b, c)
  7. }
  8. t.Logf("%d + %d =%d", a, b, c)
  9. }
  10. func TestSub(t *testing.T) {
  11. a := 10
  12. b := 2
  13. c := Sub(a, b)
  14. if c != 8 {
  15. t.Fatalf("err:%d - %d = %d", a, b, c)
  16. }
  17. t.Logf("%d - %d =%d", a, b, c)
  18. }

压力/性能测试benchmark

对于性能测试函数来说,其名称必须以Benchmark为前缀,并且唯一参数的类型必须是*testing.B类型的。
testing.B 拥有testing.T 的全部接口,同时还可以统计内存消耗,指定并行数目和操作计时器等

  1. import "testing"
  2. func BenchmarkFoo(t *testing.B) {
  3. t.Log("Benchmark")
  4. }

性能测试demo

  1. package test
  2. import (
  3. "bytes"
  4. "strings"
  5. "testing"
  6. )
  7. func BenchmarkByte(t *testing.B) {
  8. b := bytes.Buffer{}
  9. b.WriteString("foo")
  10. for i := 0; i < t.N; i++ {
  11. b.String()
  12. }
  13. }
  14. func BenchmarkStr(t *testing.B) {
  15. s := strings.Builder{}
  16. s.WriteString("test")
  17. for i := 0; i < t.N; i++ {
  18. s.String()
  19. }
  20. }

go test 不会主动执行benchmark函数的,需要增建 -test_bench,所以下面的代码不会执行任何压力测试

  1. go test bench_test.go
  2. ok command-line-arguments 0.001s [no tests to run]

“.*”表示测试全部的压力测试函数,执行当前测试文件的所有压力测试函数,第一列表示被执行的测试函数,-8代表当前的cup执行核数,第二列表示执行了总共次数,第三列表示平均执行的耗时

  1. go test bench_test.go -test.bench=".*"
  2. goos: linux
  3. goarch: amd64
  4. BenchmarkByte-8 200000000 6.17 ns/op
  5. BenchmarkStr-8 2000000000 0.34 ns/op
  6. PASS
  7. ok command-line-arguments 2.577s

执行单个的测试函数

  1. go test bench_test.go -test.bench="Str"
  2. goos: linux
  3. goarch: amd64
  4. BenchmarkStr-8 2000000000 0.34 ns/op
  5. PASS
  6. ok command-line-arguments 0.719s

go test

go test +包名,执行这个包下面的所有测试用例
go test +测试源文件,执行这个测试源文件里的所有测试用例
go test -run 函数名称,执行只定的测试用例
go test -v 打印日志

调试

delve是golang推荐的专门go语言调试工具,用来替代gdb,因为:golang组织说delve能更好的理解go语言。
Go基础——Test - 图1

Mac OS 安装Delve
首先需要安装xcode-select --install,
window和linux 执行go get 命令

  1. go get github.com/derekparker/delve/cmd/dlv

当前调试程序如下

  1. package main
  2. import "net/http"
  3. func hello(writer http.ResponseWriter, request *http.Request) {
  4. host := request.Host
  5. writer.Write([]byte(host))
  6. }
  7. func main() {
  8. http.HandleFunc("/hello",hello)
  9. http.ListenAndServe(":8080",nil)
  10. }

执行 dlv debug main.go,b命令是设置断点,发送请求命令curl localhost:8080/hello

  1. (dlv) b main.hello
  2. Breakpoint 1 set at 0x1330e73 for main.hello() ./main.go:5
  3. (dlv) c
  4. > main.hello() ./main.go:5 (hits goroutine(18):1 total:1) (PC: 0x1330e73)
  5. 1: package main
  6. 2:
  7. 3: import "net/http"
  8. 4:
  9. => 5: func hello(writer http.ResponseWriter, request *http.Request) {
  10. 6: host := request.Host
  11. 7: writer.Write([]byte(host))
  12. 8: }
  13. 9:
  14. 10: func main() {
  15. (dlv) n
  16. > main.hello() ./main.go:6 (PC: 0x1330e81)
  17. 1: package main
  18. 2:
  19. 3: import "net/http"
  20. 4:
  21. 5: func hello(writer http.ResponseWriter, request *http.Request) {
  22. => 6: host := request.Host
  23. 7: writer.Write([]byte(host))
  24. 8: }
  25. 9:
  26. 10: func main() {
  27. 11: http.HandleFunc("/hello",hello)
  28. (dlv) n
  29. > main.hello() ./main.go:7 (PC: 0x1330ea3)
  30. 2:
  31. 3: import "net/http"
  32. 4:
  33. 5: func hello(writer http.ResponseWriter, request *http.Request) {
  34. 6: host := request.Host
  35. => 7: writer.Write([]byte(host))
  36. 8: }
  37. 9:
  38. 10: func main() {
  39. 11: http.HandleFunc("/hello",hello)
  40. 12: http.ListenAndServe(":8080",nil)
  41. (dlv) args
  42. writer = net/http.ResponseWriter(*net/http.response) 0xc000115968
  43. request = ("*net/http.Request")(0xc000144100)
  44. (dlv) p host
  45. "localhost:8080"

输入 n 回车,执行到下一行
输入s 回车,单步执行
输入 print(别名p)输出变量信息  
输入 args 打印出所有的方法参数信息
输入 locals 打印所有的本地变量

二进制文件调试

go build main.go,执行当前./demo 程序

  1. ps -ef|grep demo
  2. 501 11215 554 0 12:11上午 ttys001 0:00.01 ./demo
  3. 501 11280 11218 0 12:11上午 ttys002 0:00.00 grep demo

dlv attch 11215

  1. (dlv) s
  2. > main.hello() ./go/src/baxiang.cn/demo/main.go:7 (PC: 0x124f620)
  3. Warning: debugging optimized function
  4. 2:
  5. 3: import "net/http"
  6. 4:
  7. 5: func hello(writer http.ResponseWriter, request *http.Request) {
  8. 6: host := request.Host
  9. => 7: writer.Write([]byte(host))
  10. 8: }
  11. 9:
  12. 10: func main() {
  13. 11: http.HandleFunc("/hello",hello)
  14. 12: http.ListenAndServe(":8080",nil)
  15. (dlv) locals
  16. host = "localhost:8080"

go tool

  1. import "fmt"
  2. func main() {
  3. fmt.Println("foo")
  4. return
  5. fmt.Printf("bar")
  6. }

在golang1.12上已经换成了go vet

  1. go tool vet main.go
  2. vet: invoking "go tool vet" directly is unsupported; use "go vet"

下面执行结果表示当前代码行无法被执行的

  1. go vet main.go
  2. # command-line-arguments
  3. ./main.go:8:2: unreachable code

分析锁的问题

  1. import (
  2. "sync"
  3. )
  4. type Foo struct {
  5. lock sync.Mutex
  6. }
  7. func (f *Foo) Lock() {
  8. f.lock.Lock()
  9. }
  10. func (f Foo) Unlock() {
  11. f.lock.Unlock()
  12. }
  13. func main() {
  14. f := Foo{sync.Mutex{}}
  15. f.Lock()
  16. f.Unlock()
  17. f.Lock()
  18. }

t.lock.Unlock() 实际上是由 lock 的副本调用的。在锁传值使用了值传递 需要修改,否则出现死锁。

  1. go vet main.go
  2. # command-line-arguments
  3. ./main.go:15:9: Unlock passes lock by value: command-line-arguments.Foo

goconvey

https://github.com/smartystreets/goconvey

testify

https://github.com/stretchr/testify