Go 语言单元测试 | 青训营

61 阅读3分钟

测试

测试方法:

  1. 回归测试:手动对主流场景进行测试
  2. 集成测试:对系统的一个功能模块进行验证,检查是否能系统中的其它模块一起正常工作
  3. 单元测试:对单独的函数模块进行验证,检查是否能对指定输入做出正确的输出

从上至下,覆盖率逐层增加,成本逐层降低。

单元测试

  • 所有测试文件以_test.go结尾
  • 对函数Xxx的单元测试声明为func TestXxx(t *testing.T)
  • 一个测试文件的初始化/收尾工作写在func TestMain(m *testing.M)函数中
    • 调用m.Run()执行所有测试

*testing.T对象方法:

  • Errorf(fstring, ...)在标准错误流中打印一个格式化字符串

也可以使用github.com/stretchr/testify/assert包中的assert.Equal(t, expectedOutput, output)简化错误打印代码。

设计单元测试

  • 测试分支相互独立、全面覆盖
  • 测试单元力度足够小
  • 被测试的函数职责单一

运行单元测试

运行项目中所有单元测试的命令:go test [flags] [packages]

测试覆盖率

测试覆盖率用于衡量代码是否经过了完备的测试,更高的测试覆盖率通常可以保证更好的代码正确性。

  • 使用go test XX_test.go XX.go --cover获取XX.go中代码的测试覆盖率
  • 实际项目中代码覆盖率一般为50%-60%

单元测试中的依赖

被测试的代码单元可能需要外部依赖,例如:文件、数据库、缓存

单元测试的设计目标:

  • 稳定:任何单元测试能够在任何时间独立运行
  • 幂等:重复运行一个测试的结果总是相同

举例:文件依赖

假设我们需要对一个读取文件的函数进行测试,这个函数通过一个固定的路径访问本地文件,读取文本并返回。

这个文件可能被删除或修改,从而导致测试结果不稳定。

Mock

为保证单元测试的稳定性与幂等性,可以使用Mock,即使用另一个函数替换被测试代码中的一个函数,模拟对特定依赖的访问。

  • 这里使用github.com/bouk/monkey进行Mock。

在单元测试开始时,使用monkey.Patch(<func>, <replacement_func>)将所有对函数<func>的调用替换为对<replacement_func>的调用,这个操作被称为打桩

在单元测试结束时,使用monkey.Unpatch(<func>)解除对函数<func>调用的替换。

  • 可以使用defer monkey.Unpatch(<func>)

基准测试

基准测试方法与单元测试类似,但主要目的是分析代码单元的性能

  • 基准测试函数命名为BenchmarkXXX
  • 入参:b *testing.B

计时:

  • 计时默认从进入函数开始
  • b.ResetTimer()重置计时器,用于跳过对准备数据的代码的计时
  • 计时在函数结束时结束

并行测试:

  • 测试多线程并行时函数的性能
b.RunParallel(func(pb *testing.PB){
    for pb.Next(){
        // Call the function being tested
    }
})

例如:rand()为了保证全局随机性,设计为线程安全的操作,这使得多个线程调用rand()时需要获取线程锁,造成性能下降。