GO/testing包

前言

之前在写GO单元测试的时候, 使用了这个结构testing.T. 进来无事翻了翻, 发现testing包中还有一些其他的结构体, 想来是不同用处. 没想到GOtesting包竟然默默做了这么多支持, 之前竟然不知道.

testing包中包含一下结构体:

  • testing.T: 这就是我们平常使用的单元测试
  • testing.F: 模糊测试, 可以自动生成测试用例
  • testing.B: 基准测试. 对函数的运行时间进行统计.
  • testing.M: 测试的钩子函数, 可预置测试前后的操作.
  • testing.PB: 测试时并行执行.

依次对GO的各个测试类型进行介绍.

以下各项测试中出现的方法Reverse如下:

// 此方法源自 Go 官方文档
func Reverse(s string) string {
    bs := []byte(s)
    length := len(bs)
    for i := 0; i < length/2; i++ {
        bs[i], bs[length-i-1] = bs[length-i-1], bs[i]
    }
    return string(bs)
}

testing.T

用于进行单元测试. 官方文档

Go对单元测试函数要求如下:

  1. 文件名形如: xxx_test.go
  2. 函数签名形如: func TestXxx(t *testing.T)

我们创建文件lib_test.go, 并在其中定义如下方法:

func TestReverse(t *testing.T) {
    str := "abc"
    revStr1 := Reverse(str)
    revStr2 := Reverse(revStr1)
    if str != revStr2 {
        // error 方法报错后, 会继续向下执行
        t.Error("error")
        // fatal 方法报错后, 会退出测试
        // t.Fatal("fatal")
        // 输出调试信息
        // t.Log("log")
        // 测试中断, 但是测试结果不会十遍
        // t.Skip("skip")
    }
  // 可启动多个子测试, 子测试之间并行运行
    for _, str = range []string{"abcd", "aceb"} {
    // 第一个参数为子测试的标识
        t.Run(str, func(t *testing.T) {
            revStr1 := Reverse(str)
            revStr2 := Reverse(revStr1)
            if str != revStr2 {
                t.Error("error")
            }
        })
    }
}

使用如下命令运行测试用例(test.run 指定运行某一个函数):

go test -test.run TestReverse

这就是单元测试的简单应用了, 是不是so easy啦.

testing.F

用于模糊测试, 会自动生成测试用例. 官方文档

其内部会自动生成各种测试用例, 并自动调用执行. Go对模糊测试的函数要求如下:

  1. 文件名形如: xxx_test.go
  2. 函数签名形如: func FuzzXxx(f *testing.F)

其测试函数定义如下:

func FuzzReverse(f *testing.F) {
    // 设置测试用例需要随机生成的变量类型
    f.Add("Hello, world!")
    // 生成测试用例并进行测试. 回电函数接收的参数, 与 f.Add 设置的参数类型一致
    f.Fuzz(func(t *testing.T, str string) {
        revStr1 := Reverse(str)
        revStr2 := Reverse(revStr1)
        if revStr2 != str {
            t.Error("error")
        }
        // 判断是否是合法的 utf8 编码
        if utf8.ValidString(str) && !utf8.ValidString(revStr1) {
            t.Error("utf8 error")
        }
    })
}

运行命令开始测试: go test -test.fuzz FuzzReverse -test.run ^$ (其中test.run指定不运行test函数)

image-20220528091719962

当测试失败的时候, 失败的用力会写入指定的文件, 文件在控制台输出.

testing.B

用于基准测试. 对函数的运行时间进行统计. , 对函数要求如下:

  1. 文件名形如: xxx_test.go
  2. 函数签名形如: func BenchmarkXxx(b *testing.B)

函数定义如下:

func BenchmarkReverse(b *testing.B) {
  // 打开内存统计
  b.ReportAllocs()
    // 按照要求运行 n 遍
    for i := 0; i < b.N; i++ {
        Reverse("hello")
    }
}

运行命令: go test -test.bench BenchmarkReverse -test.run ^$

image-20220528100115791

结果中指出了运行次数及平均时间. 其中各项值得含义如下:

  • 100000000: 迭代次数

  • ns/op: 平均每次迭代消耗的时间

  • B/op: 平均每次迭代消耗的内存

  • allocs/op: 平均每次迭代内存的分配次数

testing.M

定义在运行测试的前后执行的操作. 对函数的要求如下:

  1. 文件名形如: xxx_test.go
  2. 函数签名为: func TestMain(m *testing.M)

函数定义如下:

func TestMain(m *testing.M) {
    // 测试之前执行的操作
    fmt.Println("starting test main")
    // 运行测试
    code := m.Run()
    // 测试之后执行的操作
    fmt.Println("ending test main")
    os.Exit(code)
}

此函数会在运行所有测试时自动调用.

testing.PB

用于在测试时进行并发测试. 上面的 单元测试/模糊测试/基准测试 都可以使用. 以基准测试为例, 使用如下:

// 充分利用 CPU 资源, 并行执行 n 次
func BenchmarkReverse2(b *testing.B) {
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            // 此循环体总共执行 b.N 次
            Reverse("hello")
        }
    })
}

如此便可并行执行啦.


好, 有关Go的单元测试, 到这里就差不多了. 以上这些已经基本能够满足日常使用了


原文地址 https://hujingnb.com/archives/798

转载请保留原文连接: GO/testing包 | 烟草的香味

订阅评论
提醒
guest
0 评论
内联反馈
查看所有评论
0
希望看到您的想法,请发表评论。x