一文详解Go 单元测试与基准测试

497 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情

1. 单元测试

1.1 单元测试的编写规则

  • 所有测试文件以_test.go结尾
  • 函数以func TestXxx(t *testing.T)命名
  • 初始化逻辑放在func TestMain(m *testing.M)当中

image.png

1.2 t *testing.T的方法

1)当我们遇到一个断言错误的时候,标识这个测试失败,会使用到:

t.Fail() : 测试失败,测试继续,也就是之后的代码依然会执行
t.FailNow() : 测试失败,测试中断

在 t.FailNow() 方法实现的内部,是通过调用 runtime.Goexit() 来中断测试的。

2)当我们遇到一个断言错误,只希望跳过这个错误,但是不希望标识测试失败,会使用到:

t.SkipNow() : 跳过测试,测试中断

在 t.SkipNow() 方法实现的内部,是通过调用 runtime.Goexit() 来中断测试的。

3)当我们只希望打印信息,会用到 :

t.Log() : 输出信息
t.Logf() : 输出格式化的信息

注意:默认情况下,单元测试成功时,它们打印的信息不会输出,可以通过加上 -v 选项,输出这些信息。但对于基准测试,它们总是会被输出。

4)当我们希望跳过这个测试,并且打印出信息,会用到:

t.Skip() : 相当于 Log + SkipNow
t.Skipf() : 相当于 Logf + SkipNow

5)当我们希望断言失败的时候,标识测试失败,并打印出必要的信息,但是测试继续,会用到:

t.Error() : 相当于 Log + Fail
t.Errorf() : 相当于 Logf + Fail

6)当我们希望断言失败的时候,标识测试失败,打印出必要的信息,但中断测试,会用到:

t.Fatal() : 相当于 Log + FailNow
t.Fatalf() : 相当于 Logf + FailNow

2. 基准测试

2.1 基准测试介绍

当我们尝试去优化代码的性能时,首先得知道当前的性能怎么样。Go 语言标准库内置的 testing 测试框架提供了基准测试(benchmark)的能力,能让我们很容易地对某一段代码进行性能测试。

  • 函数以func BenchmarkHello(b *testing.B)命名的为基准测试

2.2 t *testing.B的方法

b.N

benchmark 用例的参数 b *testing.B,有个属性 b.N 表示这个用例需要运行的次数。b.N 对于每个用例都是不一样的。

那这个值是如何决定的呢?b.N 从 1 开始,如果该用例能够在 1s 内完成,b.N 的值便会增加,再次执行。b.N 的值大概以 1, 2, 3, 5, 10, 20, 30, 50, 100 这样的序列递增,越到后面,增加得越快。

计时方法

有三个方法用于计时:

  1. b.StartTimer():开始对测试进行计时。该方法会在基准测试开始时自动被调用,我们也可以在调用 StopTimer 之后恢复计时;
  2. b.StopTimer():停止对测试进行计时。当你需要执行一些复杂的初始化操作,并且你不想对这些操作进行测量时,就可以使用这个方法来暂时地停止计时;
  3. b.ResetTimer():对已经逝去的基准测试时间以及内存分配计数器进行清零。对于正在运行中的计时器,这个方法不会产生任何效果。本节开头有使用示例。

并行执行

通过 b.RunParallel() 方法能够并行地执行给定的基准测试。b.RunParallel()会创建出多个 goroutine,并将 b.N 分配给这些 goroutine 执行,其中 goroutine 数量的默认值为 GOMAXPROCS。

2.3 基准测试结果

对上述结果中的每一项,你是否都清楚是什么意思呢?

  • 2000000 :基准测试的迭代总次数 b.N
  • 898 ns/op:平均每次迭代所消耗的纳秒数
  • 368 B/op:平均每次迭代内存所分配的字节数
  • 9 allocs/op:平均每次迭代的内存分配次数

testing 包中的 BenchmarkResult 类型能为你提供帮助,它保存了基准测试的结果,定义如下:

type BenchmarkResult struct {
    N         int           // The number of iterations. 基准测试的迭代总次数,即 b.N
    T         time.Duration // The total time taken. 基准测试的总耗时
    Bytes     int64         // Bytes processed in one iteration. 一次迭代处理的字节数,通过 b.SetBytes 设置
    MemAllocs uint64        // The total number of memory allocations. 内存分配的总次数
    MemBytes  uint64        // The total number of bytes allocated. 内存分配的总字节数
}