Go 语言代码测试 | 青训营笔记

119 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天。

单元测试

单元测试是软件测试的一类,测试对象是最基础的代码单元(函数、类、模块),属于白盒测试。通过单元测试可以及早发现代码错误,降低后期排查、修复问题时的工作量。

单元测试规范:

  • 测试文件名为 xxx_test.go ,一般 xxx 为被测试源文件名,这样展开目录后可以清晰地看出哪些是项目源代码,哪些是测试代码
  • 测试函数以 func TestXxx(*testing.T) 的方式命名,注意 Xxx 的首字母为大写

通常只有满足以上规范,编译器才上才会有运行标志。

例如我们在 hello.go 文件中编写了一个 hello() 函数:

// 这里的函数名可以不用大写
func hello() string{
    return "bey"
}

现在我们需要测试 hello() 函数,于是在对应的 hello_test.go 文件中编写单元测试函数 TestHello()

func TestHello(t *testing.T) {
    output := hello()
    expectOutput := "hello"
    if output != expectOutput {
        t.Errorf("Expected %s do not match actual %s", expectOutput, output)
    }
}

这里我们期望输出为 expectOutputhello() 函数的实际输出为 output,我们将期望输出与实际输出作对比,如果不一致则打印错误信息,代表测试未通过。

如果文件和函数命名符合规范,那么在编译器里应该会在测试函数旁显示测试运行图标:

image-20230205232026702

点击启动测试,可以看到测试没有通过。

image-20230205232609822

根据测试结果修改我们的代码:

func hello() string{
    // return "bye"
    return "hello"
}

再次运行测试:

image-20230205232917273

可以看到这一次我们的测试就顺利通过了。

有许多开源的包封装好了测试常用的功能,可以用来简化我们的测试代码,比如 testify 的 assert 包:

import "github.com/stretchr/testify/assert"func TestHello_2(t *testing.T) {
    output := hello()
    expectOutput := "hello"
    assert.Equal(t, expectOutput, output)
}

基准测试

基准测试用来测试函数的执行效率,它也有与单元测试类似的规范。

基准测试规范:

  • 文件名以 _test.go 结尾
  • 测试函数以 func BenchmarkXxx(*testing.B) 的方式命名

同样的,通常只有满足以上规范,编译器才上才会有运行标志

例如,我们在 fib.go 文件中编写了一个 fib() 函数用于计算 Fibonacci 前 n 位数:

func fib(x int) int {
    if x <= 2 {
        return 1
    }
    return fib(x-1) + fib(x-2)
}

现在我们需要测试 fib() 函数的性能,于是在对应的 fib_test.go 文件中编写基准测试函数 BenchmarkFib()

func BenchmarkFib(b *testing.B) {
    // 一些初始化操作...
    // 重置计时器,去除初始化操作用时,这里没有初始化,因此也可以不用
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        fib(i)
    }
}

循环里的 b.N 代表运行次数,在基准测试中这个 b.N 会逐渐递增,直到程序恰好能在 1s 内跑完。基准测试默认运行时间是 1s,也可以通过参数手动调整运行时间,或者直接修改运行次数。

同样的,这此我们应该可以在 BenchmarkFib() 函数旁边找到测试运行图标,点击运行基准测试,我们可以看到类似下图的测试结果,这里还顺便加入了迭代版的 fastFib() 对应的基准测试函数:

image-20230121235157639

在这份测试结果中:

BenchmarkFib-12BenchmarkFastFib-12 中的 -12 代表使用的核心数,默认为 CPU 总核心数。

42822087081 分别代表两个测试的运行次数,也就是 b.N 递增到最后的值。

2819614 ns/op52.58 ns/op 分别代表两个测试的单次操作平均用时。

接下来的两列则为单次操作平均内存占用和单次操作平均内存申请次数。

关于 Mock 函数

简单来说就是在程序调用函数 A 时使用另一个函数 B 替代执行,而函数 B 并不需要人为的调用,不用破坏源代码,它的原理是在程序运行时将函数 A 的内存地址修改为函数 B的地址。一般 Mock 用于构造一个稳定的测试环境,例如某个函数用于获取服务器上的文件内容,由于服务器上的文件可能会被修改,从而导致两次测试的结果不同,这时就可以写一个 Mock 函数代替该函数执行,返回一个固定的结果以供测试。