TOC
CHAT

testing测试包:单元测试、基准测试

Go后端开发进阶教程——测试。

Go语言的测试使用内置的testing包来实现,该包专门用于编写和运行测试代码。

1 单元测试

  • 企业中一般的测试覆盖率:50% ~ 60%
  • 重点业务例如资金型业务:80%+

单元测试是验证代码的最小可测试单元(通常是函数)的正确性的过程。

1.1 测试文件与函数的命名

测试文件通常以_test.go结尾,例如,如果有一个mymath.go文件,那么其测试文件应为mymath_test.go

在测试文件中,测试函数必须以Test开头,并接受一个*testing.T类型的参数。例如:

func Test_myFunction(t *testing.T) {
    // 测试代码
}

1.2 断言

testing包提供了一些方法来验证测试结果是否符合预期,这些方法会记录测试是否通过,并在失败时提供详细的错误信息。

常用的断言方法:

  • t.Errorf:用于报告错误,并终止当前测试函数。
  • t.FailNow:用于立即失败并停止当前测试函数。
  • t.Fatalf:用于报告错误,并终止当前测试函数,同时终止整个测试。
  • t.Skip:用于跳过当前测试函数。

1.3 表驱动测试

Go支持表驱动测试,通过将多个测试用例组织在一个循环中来减少重复代码:

func Test_myFunction(t *testing.T) {
    tests := []struct {
        input    int
        expected int
    }{
        {5, 10},
        {10, 20},
        // 更多测试用例...
    }

    for _, tt := range tests {
        result := myFunction(tt.input)
        assert.Equal(t, result, tt.expected)
    }
}

1.4 常用命令

  • 运行测试:使用go test命令来测试单个文件或运行特点包中的所有测试。例如:
go test mymath_test.go

go test mypackage
  • 测试覆盖率:Go支持测试覆盖率报告,可以使用以下命令生成:
go test -cover mypackage
  • 并行测试:Go允许并行运行测试以提高效率,可以通过设置-parallel标志来指定并行度。例如:
go test -parallel 10 mypackage

2 Mock和Stub

Mock测试和打桩测试是软件测试中常用的技术,它们用于模拟外部依赖或系统组件的行为,以便在隔离的环境中测试代码。

Mock测试用于模拟外部依赖或系统组件(如数据库、网络服务、文件系统等)的行为。在单元测试中,Mock对象被用来替代真实的依赖项,以便可以控制这些依赖项的行为和返回值,从而专注于测试当前单元的功能。

Mock 的主要目的包括:

  1. 隔离测试:确保测试只关注当前的代码单元,不受外部系统的影响。
  2. 控制行为:可以预设Mock对象的行为和返回值,使得测试更加可预测。
  3. 提高测试速度:通过避免真实的数据库操作或网络请求,可以加快测试执行速度。
  4. 测试异常情况:可以模拟异常情况,如网络超时、数据库错误等,以测试代码的健壮性。

打桩测试是一种测试方法,其中打桩(Stub)是被测试模块的简化版本,它提供了必要的接口和行为,但不包含实际的业务逻辑。打桩用于模拟外部系统或模块的行为,以便在测试中替代真实的依赖。

打桩的主要特点包括:

  1. 简化接口:打桩提供了与真实依赖相同的接口,但实现更简单,只包含测试所需的基本功能。
  2. 返回固定值:打桩可以返回固定的值或响应,以便测试特定的场景。
  3. 模拟延迟:在网络服务的测试中,打桩可以模拟网络延迟或超时。
  4. 减少依赖:通过使用打桩,可以减少对外部系统的依赖,使得测试更加独立和可靠。

虽然Mock和Stub都是用于模拟外部依赖的技术,但它们之间有一些区别:

  • Mock:通常更复杂,可以模拟更丰富的行为和交互,包括验证方法调用的次数、顺序和参数等。
  • Stub:通常更简单,只提供必要的接口和固定的行为,不包含复杂的交互验证。

在实际应用中,Mock和Stub可以结合使用,以实现更全面的测试覆盖。例如,可以使Mock来验证与外部系统的交互,同时使用Stub来提供必要的接口和行为。


3 基准测试

基准测试(Benchmarking)是一种测量和评估软件性能指标的活动。可以在某个时候通过基准测试建立一个已知的性能水平(称为基准线),当系统的软硬件环境发生变化之后再进行一次基准测试以确定那些变化对性能的影响。这是基准测试最常见的用途。其他用途包括测定某种负载水平下的性能极限、管理系统或环境的变化、发现可能导致性能问题的条件等。

在系统上运行一系列测试程序并把性能计数器的结果保存起来,这些结构称为“性能指标”。性能指标通常都保存或归档,并在系统环境的描述中进行注解。例如有经验的数据库专业人员会把基准测试的结果以及当时的系统配置和环境一起存入他们的档案。这可以让他们对系统过去和现在的性能表现进行对照比较,确认系统或环境的所有变化。

3.1 常用api

  • 与单元测试类似,基准测试以Benchmark开头,参数是*testing.B
  • b中的N属性反复递增循环测试(对一个测试用例的默认测试时间是1秒,若测试用例函数返回时还不到1秒,那么该N值将按1、2、5、10、20、50…递增,并以递增后的值重新进行用例函数测试)
  • b调用ResetTimer重置之前所做的init或其他不应该划入基准测试范围的准备操作。
  • b调用RunParallel进行多协程并发测试,所传函数的参数应为*testing.PB,详见下例。

3.2 综合案例

【例】测试随机选择执行服务器(测试时可注意到代码在并发情况下存在劣化,主要原因是rand为了保证全局的随机性和并发安全,持有了一把全局锁)

import(
    "math/rand"
)

var ServerIndex [10]int

func InitServerIndex() {
    for i := 0; i < 10; i++ {
        ServerIndex[i] = i+100
    }
}

func Select int {
    return ServerIndex[rand.Intn(10)]
}

// 进行基准测试
func BenchmarkSelect(b *testing.B) {
    InitServerIndex()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        Select()
    }
}

// 进行多协程并发测试
func BenchmarkSelectParallel(b *testing.B) {
    InitServerIndex()
    b.ResetTimer()
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            Select()
        }
    })
}

发表评论