简介

testing是 Go 语言标准库自带的测试库。在 Go 语言中编写测试很简单,只需要遵循 Go 测试的几个约定,与编写正常的 Go 代码没有什么区别。Go 语言中有 3 种类型的测试:单元测试,性能测试,示例测试。下面依次来介绍。

单元测试

单元测试又称为功能性测试,是为了测试函数、模块等代码的逻辑是否正确。接下来我们编写一个库,用于将表示罗马数字的字符串和整数互转。罗马数字是由M/D/C/L/X/V/I这几个字符根据一定的规则组合起来表示一个正整数:

  • M=1000,D=500,C=100,L=50,X=10,V=5,I=1;
  • 只能表示 1-3999 范围内的整数,不能表示 0 和负数,不能表示 4000 及以上的整数,不能表示分数和小数(当然有其他复杂的规则来表示这些数字,这里暂不考虑);
  • 每个整数只有一种表示方式,一般情况下,连写的字符表示对应整数相加,例如I=1II=2III=3。但是,十位字符I/X/C/M)最多出现 3 次,所以不能用IIII表示 4,需要在V左边添加一个I(即IV)来表示,不能用VIIII表示 9,需要使用IX代替。另外五位字符V/L/D)不能连续出现 2 次,所以不能出现VV,需要用X代替。
  1. // roman.go
  2. package roman
  3. import (
  4. "bytes"
  5. "errors"
  6. "regexp"
  7. )
  8. type romanNumPair struct {
  9. Roman string
  10. Num int
  11. }
  12. var (
  13. romanNumParis []romanNumPair
  14. romanRegex *regexp.Regexp
  15. )
  16. var (
  17. ErrOutOfRange = errors.New("out of range")
  18. ErrInvalidRoman = errors.New("invalid roman")
  19. )
  20. func init() {
  21. romanNumParis = []romanNumPair{
  22. {"M", 1000},
  23. {"CM", 900},
  24. {"D", 500},
  25. {"CD", 400},
  26. {"C", 100},
  27. {"XC", 90},
  28. {"L", 50},
  29. {"XL", 40},
  30. {"X", 10},
  31. {"IX", 9},
  32. {"V", 5},
  33. {"IV", 4},
  34. {"I", 1},
  35. }
  36. romanRegex = regexp.MustCompile(`^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$`)
  37. }
  38. func ToRoman(n int) (string, error) {
  39. if n <= 0 || n >= 4000 {
  40. return "", ErrOutOfRange
  41. }
  42. var buf bytes.Buffer
  43. for _, pair := range romanNumParis {
  44. for n > pair.Num {
  45. buf.WriteString(pair.Roman)
  46. n -= pair.Num
  47. }
  48. }
  49. return buf.String(), nil
  50. }
  51. func FromRoman(roman string) (int, error) {
  52. if !romanRegex.MatchString(roman) {
  53. return 0, ErrInvalidRoman
  54. }
  55. var result int
  56. var index int
  57. for _, pair := range romanNumParis {
  58. for roman[index:index+len(pair.Roman)] == pair.Roman {
  59. result += pair.Num
  60. index += len(pair.Roman)
  61. }
  62. }
  63. return result, nil
  64. }

在 Go 中编写测试很简单,只需要在待测试功能所在文件的同级目录中创建一个以_test.go结尾的文件。在该文件中,我们可以编写一个个测试函数。测试函数名必须是TestXxxx这个形式,而且Xxxx必须以大写字母开头,另外函数带有一个*testing.T类型的参数:

  1. // roman_test.go
  2. package roman
  3. import (
  4. "testing"
  5. )
  6. func TestToRoman(t *testing.T) {
  7. _, err1 := ToRoman(0)
  8. if err1 != ErrOutOfRange {
  9. t.Errorf("ToRoman(0) expect error:%v got:%v", ErrOutOfRange, err1)
  10. }
  11. roman2, err2 := ToRoman(1)
  12. if err2 != nil {
  13. t.Errorf("ToRoman(1) expect nil error, got:%v", err2)
  14. }
  15. if roman2 != "I" {
  16. t.Errorf("ToRoman(1) expect:%s got:%s", "I", roman2)
  17. }
  18. }

在测试函数中编写的代码与正常的代码没有什么不同,调用相应的函数,返回结果,判断结果与预期是否一致,如果不一致则调用testing.TErrorf()输出错误信息。运行测试时,这些错误信息会被收集起来,运行结束后统一输出。

测试编写完成之后,使用go test命令运行测试,输出结果:

  1. $ go test
  2. --- FAIL: TestToRoman (0.00s)
  3. roman_test.go:18: ToRoman(1) expect:I got:
  4. FAIL
  5. exit status 1
  6. FAIL github.com/go-quiz/go-daily-lib/testing 0.172s

我故意将ToRoman()函数中写错了一行代码,n > pair.Num>应该为>=,单元测试成功找出了错误。修改之后重新运行测试:

  1. $ go test
  2. PASS
  3. ok github.com/go-quiz/go-daily-lib/testing 0.178s

这次测试都通过了!

我们还可以给go test命令传入-v选项,输出详细的测试信息:

  1. $ go test -v
  2. === RUN TestToRoman
  3. --- PASS: TestToRoman (0.00s)
  4. PASS
  5. ok github.com/go-quiz/go-daily-lib/testing 0.174s

在运行每个测试函数前,都输出一行=== RUN,运行结束之后输出--- PASS--- FAIL信息。

表格驱动测试

在上面的例子中,我们实际上只测试了两种情况,0 和 1。按照这种方式将每种情况都写出来就太繁琐了,Go 中流行使用表格的方式将各个测试数据和结果列举出来:

  1. func TestToRoman(t *testing.T) {
  2. testCases := []struct {
  3. num int
  4. expect string
  5. err error
  6. }{
  7. {0, "", ErrOutOfRange},
  8. {1, "I", nil},
  9. {2, "II", nil},
  10. {3, "III", nil},
  11. {4, "IV", nil},
  12. {5, "V", nil},
  13. {6, "VI", nil},
  14. {7, "VII", nil},
  15. {8, "VIII", nil},
  16. {9, "IX", nil},
  17. {10, "X", nil},
  18. {50, "L", nil},
  19. {100, "C", nil},
  20. {500, "D", nil},
  21. {1000, "M", nil},
  22. {31, "XXXI", nil},
  23. {148, "CXLVIII", nil},
  24. {294, "CCXCIV", nil},
  25. {312, "CCCXII", nil},
  26. {421, "CDXXI", nil},
  27. {528, "DXXVIII", nil},
  28. {621, "DCXXI", nil},
  29. {782, "DCCLXXXII", nil},
  30. {870, "DCCCLXX", nil},
  31. {941, "CMXLI", nil},
  32. {1043, "MXLIII", nil},
  33. {1110, "MCX", nil},
  34. {1226, "MCCXXVI", nil},
  35. {1301, "MCCCI", nil},
  36. {1485, "MCDLXXXV", nil},
  37. {1509, "MDIX", nil},
  38. {1607, "MDCVII", nil},
  39. {1754, "MDCCLIV", nil},
  40. {1832, "MDCCCXXXII", nil},
  41. {1993, "MCMXCIII", nil},
  42. {2074, "MMLXXIV", nil},
  43. {2152, "MMCLII", nil},
  44. {2212, "MMCCXII", nil},
  45. {2343, "MMCCCXLIII", nil},
  46. {2499, "MMCDXCIX", nil},
  47. {2574, "MMDLXXIV", nil},
  48. {2646, "MMDCXLVI", nil},
  49. {2723, "MMDCCXXIII", nil},
  50. {2892, "MMDCCCXCII", nil},
  51. {2975, "MMCMLXXV", nil},
  52. {3051, "MMMLI", nil},
  53. {3185, "MMMCLXXXV", nil},
  54. {3250, "MMMCCL", nil},
  55. {3313, "MMMCCCXIII", nil},
  56. {3408, "MMMCDVIII", nil},
  57. {3501, "MMMDI", nil},
  58. {3610, "MMMDCX", nil},
  59. {3743, "MMMDCCXLIII", nil},
  60. {3844, "MMMDCCCXLIV", nil},
  61. {3888, "MMMDCCCLXXXVIII", nil},
  62. {3940, "MMMCMXL", nil},
  63. {3999, "MMMCMXCIX", nil},
  64. {4000, "", ErrOutOfRange},
  65. }
  66. for _, testCase := range testCases {
  67. got, err := ToRoman(testCase.num)
  68. if got != testCase.expect {
  69. t.Errorf("ToRoman(%d) expect:%s got:%s", testCase.num, testCase.expect, got)
  70. }
  71. if err != testCase.err {
  72. t.Errorf("ToRoman(%d) expect error:%v got:%v", testCase.num, testCase.err, err)
  73. }
  74. }
  75. }

上面将要测试的每种情况列举出来,然后针对每个整数调用ToRoman()函数,比较返回的罗马数字字符串和错误值是否与预期的相符。后续要添加新的测试用例也很方便。

分组和并行

有时候对同一个函数有不同维度的测试,将这些组合在一起有利于维护。例如上面对ToRoman()函数的测试可以分为非法值,单个罗马字符和普通 3 种情况。

为了分组,我对代码做了一定程度的重构,首先抽象一个toRomanCase结构:

  1. type toRomanCase struct {
  2. num int
  3. expect string
  4. err error
  5. }

将所有的测试数据划分到 3 个组中:

  1. var (
  2. toRomanInvalidCases []toRomanCase
  3. toRomanSingleCases []toRomanCase
  4. toRomanNormalCases []toRomanCase
  5. )
  6. func init() {
  7. toRomanInvalidCases = []toRomanCase{
  8. {0, "", roman.ErrOutOfRange},
  9. {4000, "", roman.ErrOutOfRange},
  10. }
  11. toRomanSingleCases = []toRomanCase{
  12. {1, "I", nil},
  13. {5, "V", nil},
  14. // ...
  15. }
  16. toRomanNormalCases = []toRomanCase{
  17. {2, "II", nil},
  18. {3, "III", nil},
  19. // ...
  20. }
  21. }

然后为了避免代码重复,抽象一个运行多个toRomanCase的函数:

  1. func testToRomanCases(cases []toRomanCase, t *testing.T) {
  2. for _, testCase := range cases {
  3. got, err := roman.ToRoman(testCase.num)
  4. if got != testCase.expect {
  5. t.Errorf("ToRoman(%d) expect:%s got:%s", testCase.num, testCase.expect, got)
  6. }
  7. if err != testCase.err {
  8. t.Errorf("ToRoman(%d) expect error:%v got:%v", testCase.num, testCase.err, err)
  9. }
  10. }
  11. }

为每个分组定义一个测试函数:

  1. func testToRomanInvalid(t *testing.T) {
  2. testToRomanCases(toRomanInvalidCases, t)
  3. }
  4. func testToRomanSingle(t *testing.T) {
  5. testToRomanCases(toRomanSingleCases, t)
  6. }
  7. func testToRomanNormal(t *testing.T) {
  8. testToRomanCases(toRomanNormalCases, t)
  9. }

在原来的测试函数中,调用t.Run()运行不同分组的测试函数,t.Run()第一个参数为子测试名,第二个参数为子测试函数:

  1. func TestToRoman(t *testing.T) {
  2. t.Run("Invalid", testToRomanInvalid)
  3. t.Run("Single", testToRomanSingle)
  4. t.Run("Normal", testToRomanNormal)
  5. }

运行:

  1. $ go test -v
  2. === RUN TestToRoman
  3. === RUN TestToRoman/Invalid
  4. === RUN TestToRoman/Single
  5. === RUN TestToRoman/Normal
  6. --- PASS: TestToRoman (0.00s)
  7. --- PASS: TestToRoman/Invalid (0.00s)
  8. --- PASS: TestToRoman/Single (0.00s)
  9. --- PASS: TestToRoman/Normal (0.00s)
  10. PASS
  11. ok github.com/go-quiz/go-daily-lib/testing 0.188s

可以看到,依次运行 3 个子测试,子测试名是父测试名和t.Run()指定的名字组合而成的,如TestToRoman/Invalid

默认情况下,这些测试都是依次顺序执行的。如果各个测试之间没有联系,我们可以让他们并行以加快测试速度。方法也很简单,在testToRomanInvalid/testToRomanSingle/testToRomanNormal这 3 个函数开始处调用t.Parallel(),由于这 3 个函数直接调用了testToRomanCases,也可以只在testToRomanCases函数开头出添加:

  1. func testToRomanCases(cases []toRomanCase, t *testing.T) {
  2. t.Parallel()
  3. // ...
  4. }

运行:

  1. $ go test -v
  2. ...
  3. --- PASS: TestToRoman (0.00s)
  4. --- PASS: TestToRoman/Invalid (0.00s)
  5. --- PASS: TestToRoman/Normal (0.00s)
  6. --- PASS: TestToRoman/Single (0.00s)
  7. PASS
  8. ok github.com/go-quiz/go-daily-lib/testing 0.182s

我们发现测试完成的顺序并不是我们指定的顺序。

另外,这个示例中我将roman_test.go文件移到了roman_test包中,所以需要import "github.com/go-quiz/go-daily-lib/testing/roman"。这种方式在测试包有循环依赖的情况下非常有用,例如标准库中net/http依赖net/urlurl的测试函数依赖net/http,如果把测试放在net/url包中,那么就会导致循环依赖url_test(net/url)->net/http->net/url。这时可以将url_test放在一个独立的包中。

主测试函数

有一种特殊的测试函数,函数名为TestMain(),接受一个*testing.M类型的参数。这个函数一般用于在运行所有测试前执行一些初始化逻辑(如创建数据库链接),或所有测试都运行结束之后执行一些清理逻辑(释放数据库链接)。如果测试文件中定义了这个函数,则go test命令会直接运行这个函数,否者go test会创建一个默认的TestMain()函数。这个函数的默认行为就是运行文件中定义的测试。我们自定义TestMain()函数时,也需要手动调用m.Run()方法运行测试函数,否则测试函数不会运行。默认的TestMain()类似下面代码:

  1. func TestMain(m *testing.M) {
  2. os.Exit(m.Run())
  3. }

下面自定义一个TestMain()函数,打印go test支持的选项:

  1. func TestMain(m *testing.M) {
  2. flag.Parse()
  3. flag.VisitAll(func(f *flag.Flag) {
  4. fmt.Printf("name:%s usage:%s value:%v\n", f.Name, f.Usage, f.Value)
  5. })
  6. os.Exit(m.Run())
  7. }

运行:

  1. $ go test -v
  2. name:test.bench usage:run only benchmarks matching `regexp` value:
  3. name:test.benchmem usage:print memory allocations for benchmarks value:false
  4. name:test.benchtime usage:run each benchmark for duration `d` value:1s
  5. name:test.blockprofile usage:write a goroutine blocking profile to `file` value:
  6. name:test.blockprofilerate usage:set blocking profile `rate` (see runtime.SetBlockProfileRate) value:1
  7. name:test.count usage:run tests and benchmarks `n` times value:1
  8. name:test.coverprofile usage:write a coverage profile to `file` value:
  9. name:test.cpu usage:comma-separated `list` of cpu counts to run each test with value:
  10. name:test.cpuprofile usage:write a cpu profile to `file` value:
  11. name:test.failfast usage:do not start new tests after the first test failure value:false
  12. name:test.list usage:list tests, examples, and benchmarks matching `regexp` then exit value:
  13. name:test.memprofile usage:write an allocation profile to `file` value:
  14. name:test.memprofilerate usage:set memory allocation profiling `rate` (see runtime.MemProfileRate) value:0
  15. name:test.mutexprofile usage:write a mutex contention profile to the named file after execution value:
  16. name:test.mutexprofilefraction usage:if >= 0, calls runtime.SetMutexProfileFraction() value:1
  17. name:test.outputdir usage:write profiles to `dir` value:
  18. name:test.paniconexit0 usage:panic on call to os.Exit(0) value:true
  19. name:test.parallel usage:run at most `n` tests in parallel value:8
  20. name:test.run usage:run only tests and examples matching `regexp` value:
  21. name:test.short usage:run smaller test suite to save time value:false
  22. name:test.testlogfile usage:write test action log to `file` (for use only by cmd/go) value:
  23. name:test.timeout usage:panic test binary after duration `d` (default 0, timeout disabled) value:10m0s
  24. name:test.trace usage:write an execution trace to `file` value:
  25. name:test.v usage:verbose: print additional output value:tru

这些选项也可以通过go help testflag查看。

其他

另一个函数FromRoman()我没有写任何测试,就交给大家了😀

性能测试

性能测试是为了对函数的运行性能进行评测。性能测试也必须在_test.go文件中编写,且函数名必须是BenchmarkXxxx开头。性能测试函数接受一个*testing.B的参数。下面我们编写 3 个计算第 n 个斐波那契数的函数。

第一种方式:递归

  1. func Fib1(n int) int {
  2. if n <= 1 {
  3. return n
  4. }
  5. return Fib1(n-1) + Fib1(n-2)
  6. }

第二种方式:备忘录

  1. func fibHelper(n int, m map[int]int) int {
  2. if n <= 1 {
  3. return n
  4. }
  5. if v, ok := m[n]; ok {
  6. return v
  7. }
  8. v := fibHelper(n-2, m) + fibHelper(n-1, m)
  9. m[n] = v
  10. return v
  11. }
  12. func Fib2(n int) int {
  13. m := make(map[int]int)
  14. return fibHelper(n, m)
  15. }

第三种方式:迭代

  1. func Fib3(n int) int {
  2. if n <= 1 {
  3. return n
  4. }
  5. f1, f2 := 0, 1
  6. for i := 2; i <= n; i++ {
  7. f1, f2 = f2, f1+f2
  8. }
  9. return f2
  10. }

下面我们来测试这 3 个函数的执行效率:

  1. // fib_test.go
  2. func BenchmarkFib1(b *testing.B) {
  3. for i := 0; i < b.N; i++ {
  4. Fib1(20)
  5. }
  6. }
  7. func BenchmarkFib2(b *testing.B) {
  8. for i := 0; i < b.N; i++ {
  9. Fib2(20)
  10. }
  11. }
  12. func BenchmarkFib3(b *testing.B) {
  13. for i := 0; i < b.N; i++ {
  14. Fib3(20)
  15. }
  16. }

需要特别注意的是Ngo test会一直调整这个数值,直到测试时间能得出可靠的性能数据为止。运行:

  1. $ go test -bench=.
  2. goos: windows
  3. goarch: amd64
  4. pkg: github.com/go-quiz/go-daily-lib/testing/fib
  5. cpu: Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz
  6. BenchmarkFib1-8 31110 39144 ns/op
  7. BenchmarkFib2-8 582637 3127 ns/op
  8. BenchmarkFib3-8 191600582 5.588 ns/op
  9. PASS
  10. ok github.com/go-quiz/go-daily-lib/testing/fib 5.225s

性能测试默认不会执行,需要通过-bench=.指定运行。-bench选项的值是一个简单的模式,.表示匹配所有的,Fib表示运行名字中有Fib的。

上面的测试结果表示Fib1在指定时间内执行了 31110 次,平均每次 39144 ns,Fib2在指定时间内运行了 582637 次,平均每次耗时 3127 ns,Fib3在指定时间内运行了 191600582 次,平均每次耗时 5.588 ns。

其他选项

有一些选项可以控制性能测试的执行。

-benchtime:设置每个测试的运行时间。

  1. $ go test -bench=. -benchtime=30s

运行了更长的时间:

  1. $ go test -bench=. -benchtime=30s
  2. goos: windows
  3. goarch: amd64
  4. pkg: github.com/go-quiz/go-daily-lib/testing/fib
  5. cpu: Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz
  6. BenchmarkFib1-8 956464 38756 ns/op
  7. BenchmarkFib2-8 17862495 2306 ns/op
  8. BenchmarkFib3-8 1000000000 5.591 ns/op
  9. PASS
  10. ok github.com/go-quiz/go-daily-lib/testing/fib 113.498s

-benchmem:输出性能测试函数的内存分配情况。

-memprofile file:将内存分配数据写入文件。

-cpuprofile file:将 CPU 采样数据写入文件,方便使用go tool pprof工具分析,详见我的另一篇文章《你不知道的 Go 之 pprof》

运行:

  1. $ go test -bench=. -benchtime=10s -cpuprofile=./cpu.prof -memprofile=./mem.prof
  2. goos: windows
  3. goarch: amd64
  4. pkg: github.com/go-quiz/fib
  5. BenchmarkFib1-16 356006 33423 ns/op
  6. BenchmarkFib2-16 8958194 1340 ns/op
  7. BenchmarkFib3-16 1000000000 6.60 ns/op
  8. PASS
  9. ok github.com/go-quiz/fib 33.321s

同时生成了 CPU 采样数据和内存分配数据,通过go tool pprof分析:

  1. $ go tool pprof ./cpu.prof
  2. Type: cpu
  3. Time: Aug 4, 2021 at 10:21am (CST)
  4. Duration: 32.48s, Total samples = 36.64s (112.81%)
  5. Entering interactive mode (type "help" for commands, "o" for options)
  6. (pprof) top10
  7. Showing nodes accounting for 29640ms, 80.90% of 36640ms total
  8. Dropped 153 nodes (cum <= 183.20ms)
  9. Showing top 10 nodes out of 74
  10. flat flat% sum% cum cum%
  11. 11610ms 31.69% 31.69% 11620ms 31.71% github.com/go-quiz/fib.Fib1
  12. 6490ms 17.71% 49.40% 6680ms 18.23% github.com/go-quiz/fib.Fib3
  13. 2550ms 6.96% 56.36% 8740ms 23.85% runtime.mapassign_fast64
  14. 2050ms 5.59% 61.95% 2060ms 5.62% runtime.stdcall2
  15. 1620ms 4.42% 66.38% 2140ms 5.84% runtime.mapaccess2_fast64
  16. 1480ms 4.04% 70.41% 12350ms 33.71% github.com/go-quiz/fib.fibHelper
  17. 1480ms 4.04% 74.45% 2960ms 8.08% runtime.evacuate_fast64
  18. 1050ms 2.87% 77.32% 1050ms 2.87% runtime.memhash64
  19. 760ms 2.07% 79.39% 760ms 2.07% runtime.stdcall7
  20. 550ms 1.50% 80.90% 7230ms 19.73% github.com/go-quiz/fib.BenchmarkFib3
  21. (pprof)

内存:

  1. $ go tool pprof ./mem.prof
  2. Type: alloc_space
  3. Time: Aug 4, 2021 at 10:30am (CST)
  4. Entering interactive mode (type "help" for commands, "o" for options)
  5. (pprof) top10
  6. Showing nodes accounting for 8.69GB, 100% of 8.69GB total
  7. Dropped 12 nodes (cum <= 0.04GB)
  8. flat flat% sum% cum cum%
  9. 8.69GB 100% 100% 8.69GB 100% github.com/go-quiz/fib.fibHelper
  10. 0 0% 100% 8.69GB 100% github.com/go-quiz/fib.BenchmarkFib2
  11. 0 0% 100% 8.69GB 100% github.com/go-quiz/fib.Fib2 (inline)
  12. 0 0% 100% 8.69GB 100% testing.(*B).launch
  13. 0 0% 100% 8.69GB 100% testing.(*B).runN
  14. (pprof)

示例测试

示例测试用于演示模块或函数的使用。同样地,示例测试也在文件_test.go中编写,并且示例测试函数名必须是ExampleXxx的形式。在Example*函数中编写代码,然后在注释中编写期望的输出,go test会运行该函数,然后将实际输出与期望的做比较。下面摘取自 Go 源码net/url/example_test.go文件中的代码演示了url.Values的用法:

  1. func ExampleValuesGet() {
  2. v := url.Values{}
  3. v.Set("name", "Ava")
  4. v.Add("friend", "Jess")
  5. v.Add("friend", "Sarah")
  6. v.Add("friend", "Zoe")
  7. fmt.Println(v.Get("name"))
  8. fmt.Println(v.Get("friend"))
  9. fmt.Println(v["friend"])
  10. // Output:
  11. // Ava
  12. // Jess
  13. // [Jess Sarah Zoe]
  14. }

注释中Output:后是期望的输出结果,go test会运行这些函数并与期望的结果做比较,比较会忽略空格。

有时候我们输出的顺序是不确定的,这时就需要使用Unordered Output。我们知道url.Values底层类型为map[string][]string,所以可以遍历输出所有的键值,但是输出顺序不确定:

  1. func ExampleValuesAll() {
  2. v := url.Values{}
  3. v.Set("name", "Ava")
  4. v.Add("friend", "Jess")
  5. v.Add("friend", "Sarah")
  6. v.Add("friend", "Zoe")
  7. for key, values := range v {
  8. fmt.Println(key, values)
  9. }
  10. // Unordered Output:
  11. // name [Ava]
  12. // friend [Jess Sarah Zoe]
  13. }

运行:

  1. $ go test -v
  2. $ go test -v
  3. === RUN ExampleValuesGet
  4. --- PASS: ExampleValuesGet (0.00s)
  5. === RUN ExampleValuesAll
  6. --- PASS: ExampleValuesAll (0.00s)
  7. PASS
  8. ok github.com/go-quiz/url 0.172s

没有注释,或注释中无Output/Unordered Output的函数会被忽略。

总结

本文介绍了 Go 中的 3 种测试:单元测试,性能测试和示例测试。为了让程序更可靠,让以后的重构更安全、更放心,单元测试必不可少。排查程序中的性能问题,性能测试能派上大用场。示例测试主要是为了演示如何使用某个功能。

大家如果发现好玩、好用的 Go 语言库,欢迎到 Go 每日一库 GitHub 上提交 issue😄

参考

  1. testing 官方文档: https://golang.google.cn/pkg/testing/
  2. Go 每日一库 GitHub:https://github.com/go-quiz/go-daily-lib