Go语言的单元测试与基准测试详解

 单元测试

  以一个加法函数为例,对其进行单元测试。

  首先编写add.go文件:

 
  1.  //add.go

  2.   package main

  3.   func add(a, b int) int {

  4.   return a + b

  5.   }

其次编写add_test.go文件,在go语言中,测试文件均已_test结尾,这里只需要在被测试的文件后加上_test即可。并且测试文件与要被测试的文件需要放在同一个包中,并不像Java那样需要将所有的测试文件放在一个专门的测试文件夹里面,例如我将这两个文件都放在main包下:

  1. package main

  2.   import (

  3.   "fmt"

  4.   "testing"

  5.   )

  6.   //测试函数需要以Test开头

  7.   func TestAdd(t *testing.T) {

  8.   fmt.Println("Running short test")

  9.   res := add(1, 2)

  10.   if res != 3 {

  11.   t.Errorf("add(1,2) should be 3, got %d", res)

  12.   }

  13.   }

 cd到测试文件的目录,执行测试命令go test:

  以下是运行结果:

 
  1.  (base) PS F:\GolandProjects\GoProject1\main> go test

  2.   Running short test

  3.   PASS

  4.   ok GoProject1/main 0.489s

如果想在测试中跳过那些需要耗时比较长的测试,可以做以下处理:

 
  1. package main

  2.   import (

  3.   "fmt"

  4.   "testing"

  5.   )

  6.   func TestAdd(t *testing.T) {

  7.   fmt.Println("Running short test")

  8.   res := add(1, 2)

  9.   if res != 3 {

  10.   t.Errorf("add(1,2) should be 3, got %d", res)

  11.   }

  12.   }

  13.   func TestAdd2(t *testing.T) {

  14.   if testing.Short() {

  15.   fmt.Println("Skipping long test")

  16.    //短测试模式就跳过该测试

  17.   t.Skip("Skipping long test")

  18.   }

  19.   fmt.Println("Running long test")

  20.   res := add(5, 6)

  21.   if res != 11 {

  22.   t.Errorf("add(5,6) should be 11, got %d", res)

  23.   }

  24.   }

在运行时指执行短测试,只需要执行go test -short:

 
  1.  (base) PS F:\GolandProjects\GoProject1\main> go test -short

  2.   Running short test

  3.   Skipping long test

  4.   PASS

  5.   ok GoProject1/main 0.448s

我们发现跳过了第二个测试,也就是测试函数TestAdd2。

  当然如果还是执行go test命令,则两个测试都将会运行:

 
  1.  (base) PS F:\GolandProjects\GoProject1\main> go test

  2.   Running short test

  3.   Running long test

  4.   PASS

  5.   ok GoProject1/main 0.417s

如果想要同时测试很多条数据,可以按如下的方式处理,而不需要写很多的函数:

 
  1. func TestAdd3(t *testing.T) {

  2.   var dataset = []struct {

  3.   a, b, expected int

  4.   }{

  5.   {1, 2, 3},

  6.   {5, 6, 11},

  7.   {10, 20, 30},

  8.   {100, 200, 300},

  9.   }

  10.   for _, d := range dataset {

  11.   res := add(d.a, d.b)

  12.   if res != d.expected {

  13.   t.Errorf("add(%d,%d) should be %d, got %d", d.a, d.b, d.expected, res)

  14.   }

  15.   }

  16.   }

这里我们用go test -v测试一下:

 
  1.  (base) PS F:\GolandProjects\GoProject1\main> go test -v

  2.   === RUN TestAdd

  3.   Running short test

  4.   --- PASS: TestAdd (0.00s)

  5.   === RUN TestAdd2

  6.   Running long test

  7.   --- PASS: TestAdd2 (0.00s)

  8.   === RUN TestAdd3

  9.   --- PASS: TestAdd3 (0.00s)

  10.   PASS

  11.   ok GoProject1/main 0.408s

 go test 用于运行测试并显示简洁的结果,而 go test -v 用于以详细模式运行测试并提供更多的输出信息,有助于更深入地了解测试的运行情况。通常,在开发和调试过程中,使用 -v 标志是很有帮助的,但在持续集成和自动化测试中,可能更倾向于使用简洁的 go test,以便更容易解释测试结果。

  基准测试

  性能表现需要实际数据衡量,Go语言提供了支持基准性能测试的benchmark工具。基准测试用于确定一段代码的执行速度和性能,并可以用来优化和改进代码。

  以编写斐波那契函数为例:

 
  1. //fib.go

  2.   package main

  3.   func Fib(n int) int {

  4.    if n < 2 {

  5.    return n

  6.    }

  7.    return Fib(n-1) + Fib(n-2)

  8.   }

 
  1. //fib_test.go

  2.   package main

  3.   import (

  4.    "testing"

  5.   )

  6.   func BenchmarkFib10(b *testing.B) {

  7.    for i := 0; i < b.N; i++ {

  8.    Fib(10)

  9.    }

  10.   }

 benchmark 和普通的单元测试用例一样,都位于 _test.go 文件中。

  函数名以 Benchmark 开头,参数是 b *testing.B。和普通的单元测试用例很像,单元测试函数名以 Test 开头,参数是t *testing.T。使用 b.N 控制循环次数:b.N 是基准测试的循环次数,它会根据不同的运行情况自动调整,以保证结果的可比性。

  ·运行当前 package 内的用例:go test .

  · 运行子 package 内的用例: go test ./<package name>

  · 如果想递归测试当前目录下的所有的 package:go test ./...

  go test 命令默认不运行 benchmark 用例的,如果我们想运行 benchmark 用例,需要加上 -bench 参数。例如:

 
  1. $ go test -bench .

  2.   goos: windows

  3.   goarch: amd64

  4.   pkg: GoProject1

  5.   cpu: 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz

  6.   BenchmarkFib10-16 5496252 212.5 ns/op

  7.   PASS

  8.   ok GoProject1 1.454s

 ·goos: windows:这行显示运行基准测试的操作系统,此处为 Windows。

  · goarch: amd64:这行显示运行基准测试的机器架构,此处为 64 位 AMD 架构。

  · pkg: GoProject1:这行显示包含基准测试代码的包名,此处为 “GoProject1”。

  · cpu: 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz:这行显示运行基准测试的机器 CPU 信息,包括 CPU 型号和时钟频率。

  · PASS:这行表示所有的测试,包括基准测试,都已成功通过。

  · ok GoProject1 1.454s:这行显示所有测试,包括基准测试,的整体执行时间。在这种情况下,整个测试套件执行时间大约为 1.454 秒。

  · BenchmarkFib10-16 是测试函数名,-16表示GOMAXPROCS的值为16,GOMAXPROCS 1.5版本后,默认值为CPU核数 。5496252 表示一共执行5496252 次,即b.N的值。212.5 ns/op表示每次执行花费212.5ns。

  再举一个比较详细的例子,比较不同字符串处理方式的性能:

 
  1. func Plus(n int, str string) string {

  2.    s := ""

  3.    for i := 0; i < n; i++ {

  4.    s += str

  5.    }

  6.    return s

  7.   }

  8.   func StrBuilder(n int, str string) string {

  9.    var builder strings.Builder

  10.    for i := 0; i < n; i++ {

  11.    builder.WriteString(str)

  12.    }

  13.    return builder.String()

  14.   }

  15.   func ByteBuffer(n int, str string) string {

  16.    buf := new(bytes.Buffer)

  17.    for i := 0; i < n; i++ {

  18.    buf.WriteString(str)

  19.    }

  20.    return buf.String()

  21.   }

  22.   func PreStrBuilder(n int, str string) string {

  23.   var builder strings.Builder

  24.   builder.Grow(n * len(str))

  25.   for i := 0; i < n; i++ {

  26.   builder.WriteString(str)

  27.   }

  28.   return builder.String()

  29.   }

  30.   func PreStrByteBuffer(n int, str string) string {

  31.   buf := new(bytes.Buffer)

  32.   buf.Grow(n * len(str))

  33.   for i := 0; i < n; i++ {

  34.   buf.WriteString(str)

  35.   }

  36.   return buf.String()

  37.   }

 基准测试函数:

 
  1.  func BenchmarkPlus(b *testing.B) {

  2.   for i := 0; i < b.N; i++ {

  3.   Plus(100000, "wxy")

  4.   }

  5.   }

  6.   func BenchmarkStrBuilder(b *testing.B) {

  7.   for i := 0; i < b.N; i++ {

  8.   StrBuilder(100000, "wxy")

  9.   }

  10.   }

  11.   func BenchmarkByteBuffer(b *testing.B) {

  12.   for i := 0; i < b.N; i++ {

  13.   ByteBuffer(100000, "wxy")

  14.   }

  15.   }

  16.   func BenchmarkPreStrBuilder(b *testing.B) {

  17.   for i := 0; i < b.N; i++ {

  18.   PreStrBuilder(100000, "wxy")

  19.   }

  20.   }

  21.   func BenchmarkPreByteBuffer(b *testing.B) {

  22.   for i := 0; i < b.N; i++ {

  23.   PreStrByteBuffer(100000, "wxy")

  24.   }

  25.   }

以下是运行结果:

 
  1. $ go test -bench .

  2.   goos: windows

  3.   goarch: amd64

  4.   pkg: GoProject1

  5.   cpu: 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz

  6.   BenchmarkPlus-16 1 1126084200 ns/op

  7.   BenchmarkStrBuilder-16 3982 284773 ns/op

  8.   BenchmarkByteBuffer-16 2947 485091 ns/op

  9.   BenchmarkPreStrBuilder-16 4771 278961 ns/op

  10.   BenchmarkPreByteBuffer-16 3310 364676 ns/op

  11.   PASS

  12.   ok GoProject1 6.457s

·使用+拼接性能最差,strings.Builder,bytes.Buffer相近,strings.Builder更快

· 字符串在Go语言中是不可变类型,占用内存大小是固定的

· 使用+每次都会重新分配内存

· strings.Builder, bytes.Buffer底层都是[]byte数组。内存扩容策略,不需要每次拼接重新分配内存

· 预分配内存后,strings.Builder, bytes.Buffer性能都有所提升

软件测试学习资料获取关注公众号:程序员雷叔 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值