1.9 Go 单元测试和性能测试

测试对于互联网应用软件开发来说非常重要,它对软件可靠性保证具有重要意义,通过测试能够尽可能发现并改正软件中的错误,提高软件质量。

这里我们主要讲解Go语言如何实现单元测试和性能测试。

go语言中自带有一个轻量级的测试框架testing和自带的go test命令来实现单元测试和性能测试,testing框架和其他语言中的测试框架类似,你可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压力测试用例,那么接下来让我们一一来看一下怎么写。

单元测试

创建目录test,在目录下创建add.go、add_test.go两个文件,add_test.go为单元测试文件。

add_test.go

  1. package test
  2. import "testing"
  3. func TestAdd(t *testing.T) {
  4. sum := Add(1, 2)
  5. if sum == 3 {
  6. t.Log("the result is ok")
  7. } else {
  8. t.Fatal("the result is wrong")
  9. }
  10. }
  11. func TestAdd1(t *testing.T) {
  12. t.Error("the result is error")
  13. }

add.go

  1. package test
  2. func Add(a, b int) int {
  3. return a + b
  4. }

然后在项目目录下运行go test -v就可以看到测试结果了

  1. === RUN TestAdd
  2. --- PASS: TestAdd (0.00s)
  3. add_test.go:8: the result is ok
  4. === RUN TestAdd1
  5. --- FAIL: TestAdd1 (0.00s)
  6. add_test.go:14: the result is error
  7. FAIL
  8. exit status 1
  9. FAIL _/D_/gopath/src/ados/test 0.419s

如果看到PASS字样证明测试通过,FAIL字样表示测试失败。

使用testing库的测试框架需要遵循以下几个规则如下:

  • 文件名必须是_test.go结尾的,这样在执行go test的时候才会执行到相应的代码
  • 你必须import testing这个包
  • 所有的测试用例函数必须是Test开头
  • 测试用例会按照源代码中写的顺序依次执行
  • 测试函数TestXxx()的参数是testing.T,我们可以使用该类型来记录错误或者是测试状态
  • 测试格式:func TestXxx (t *testing.T),Xxx部分可以为任意的字母数字的组合,但是首字母不能是小写字母[a-z],例如Testintdiv是错误的函数名。
  • 函数中通过调用testing.TError, Errorf, FailNow, Fatal, FatalIf方法,说明测试不通过,调用Log方法用来记录测试的信息。

性能测试或压力测试

压力测试用来检测函数(方法)的性能,和编写单元功能测试的方法类似,此处不再赘述,但需要注意以下几点:

  • 压力测试用例必须遵循如下格式,其中XXX可以是任意字母数字的组合,但是首字母不能是小写字母

    1. func BenchmarkXXX(b *testing.B) { ... }
  • go test不会默认执行压力测试的函数,如果要执行压力测试需要带上参数-test.bench,语法:-test.bench="test_name_regex",例如go test -test.bench=".*"表示测试全部的压力测试函数

  • 在压力测试用例中,请记得在循环体内使用testing.B.N,以使测试可以正常的运行
  • 文件名也必须以_test.go结尾

在test目录下创建 reflect_test.go

  1. package test
  2. import (
  3. "reflect"
  4. "testing"
  5. )
  6. type Student struct {
  7. Name string
  8. Age int
  9. Class string
  10. Score int
  11. }
  12. func BenchmarkReflect_New(b *testing.B) {
  13. var s *Student
  14. sv := reflect.TypeOf(Student{})
  15. b.ResetTimer()
  16. for i := 0; i < b.N; i++ {
  17. sn := reflect.New(sv)
  18. s, _ = sn.Interface().(*Student)
  19. }
  20. _ = s
  21. }
  22. func BenchmarkDirect_New(b *testing.B) {
  23. var s *Student
  24. b.ResetTimer()
  25. for i := 0; i < b.N; i++ {
  26. s = new(Student)
  27. }
  28. _ = s
  29. }
  30. func BenchmarkReflect_Set(b *testing.B) {
  31. var s *Student
  32. sv := reflect.TypeOf(Student{})
  33. b.ResetTimer()
  34. for i := 0; i < b.N; i++ {
  35. sn := reflect.New(sv)
  36. s = sn.Interface().(*Student)
  37. s.Name = "Jerry"
  38. s.Age = 18
  39. s.Class = "20005"
  40. s.Score = 100
  41. }
  42. }
  43. func BenchmarkReflect_SetFieldByName(b *testing.B) {
  44. sv := reflect.TypeOf(Student{})
  45. b.ResetTimer()
  46. for i := 0; i < b.N; i++ {
  47. sn := reflect.New(sv).Elem()
  48. sn.FieldByName("Name").SetString("Jerry")
  49. sn.FieldByName("Age").SetInt(18)
  50. sn.FieldByName("Class").SetString("20005")
  51. sn.FieldByName("Score").SetInt(100)
  52. }
  53. }
  54. func BenchmarkReflect_SetFieldByIndex(b *testing.B) {
  55. sv := reflect.TypeOf(Student{})
  56. b.ResetTimer()
  57. for i := 0; i < b.N; i++ {
  58. sn := reflect.New(sv).Elem()
  59. sn.Field(0).SetString("Jerry")
  60. sn.Field(1).SetInt(18)
  61. sn.Field(2).SetString("20005")
  62. sn.Field(3).SetInt(100)
  63. }
  64. }
  65. func BenchmarkDirect_Set(b *testing.B) {
  66. var s *Student
  67. b.ResetTimer()
  68. for i := 0; i < b.N; i++ {
  69. s = new(Student)
  70. s.Name = "Jerry"
  71. s.Age = 18
  72. s.Class = "20005"
  73. s.Score = 100
  74. }
  75. }

在test目录下,执行:

go test reflect_test.go -test.bench=”.*”

结果如下

  1. goos: windows
  2. goarch: amd64
  3. BenchmarkReflect_New-4 20000000 84.9 ns/op
  4. BenchmarkDirect_New-4 30000000 50.6 ns/op
  5. BenchmarkReflect_Set-4 20000000 89.9 ns/op
  6. BenchmarkReflect_SetFieldByName-4 3000000 552 ns/op
  7. BenchmarkReflect_SetFieldByIndex-4 10000000 132 ns/op
  8. BenchmarkDirect_Set-4 30000000 53.0 ns/op
  9. PASS
  10. ok command-line-arguments 10.982s

上面的结果显示我们没有执行任何TestXXX的单元测试函数,显示的结果只执行了压力测试函数,以第三行为例

BenchmarkReflect_New 函数执行了20000000次,每次的执行平均时间是84.9纳秒。最后一行 command-line-arguments 10.982s,代表总的执行时间为 10.982s。

如果只想对某个函数测试,以BenchmarkReflect_New 为例,执行命令

go test reflect_test.go -test.bench=”BenchmarkReflect_New”

结果为:

  1. goos: windows
  2. goarch: amd64
  3. BenchmarkReflect_New-4 20000000 84.9 ns/op
  4. PASS
  5. ok command-line-arguments 2.490s

如果测试整个目录下的所有测试执行:

go test -test.bench=”.*”

如果想显示内存分配的次数和大小添加 -benchmem

go test reflect_test.go -benchmem -test.bench=”.*”

  1. goos: windows
  2. goarch: amd64
  3. BenchmarkReflect_New-4 20000000 88.3 ns/op 48 B/op 1 allocs/op
  4. BenchmarkDirect_New-4 30000000 53.8 ns/op 48 B/op 1 allocs/op
  5. BenchmarkReflect_Set-4 20000000 90.9 ns/op 48 B/op 1 allocs/op
  6. BenchmarkReflect_SetFieldByName-4 3000000 564 ns/op 80 B/op 5 allocs/op
  7. BenchmarkReflect_SetFieldByIndex-4 10000000 135 ns/op 48 B/op 1 allocs/op
  8. BenchmarkDirect_Set-4 30000000 52.4 ns/op 48 B/op 1 allocs/op
  9. PASS
  10. ok command-line-arguments 12.955s

后两列代表分配的内存大小和次数(48 B/op 1 allocs/op)

推荐gotests

它是编写Go测试的一个Golang命令行工具,可以根据目标源文件的函数和方法签名生成表驱动的测试。将自动导入测试文件中的任何新依赖项。

参考:

https://studygolang.com/static/pkgdoc/pkg/testing.htm

https://www.cnblogs.com/yjf512/archive/2013/01/22/2870927.html

links