Go测试 | 青训营笔记

100 阅读3分钟

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

测试类别

  • 单元测试
  • Mock测试
  • 基准测试

单元测试

特点

  1. 所有测试文件都以”_test.go”结尾(E.g,post.go 文件对应的测试文件为 post_test.go)
  2. 测试函数都以”TestXxx(*testing.T)“方式定义(E.g func TertDBQuery(t *testing.T)

所用的包

  • testing
  • assert

覆盖率

代码覆盖率越高,即代码经过了足够的测试用例测试,项目的测试水准高。

  • 单元测试的覆盖率要达到50% ~60%(特殊业务要求的高覆盖率要达80%)
  • 测试分支相互独立、全面覆盖
  • 测试单元粒度要足够小、函数单一职责
//查看覆盖率的方法
go test 测试文件_test.go 测试文件.go --cover

例子

package main

import (
    "testing"
    "github.com/stretchr/testify/assert"
)

func add(a int, b int) int {
    return a + b
}

func TestAdd(t *testing.T) {
    expected := 3
    assert.Equal(t, expected, add(1,2))
   
}
  • assert.Equal() 的作用是判断实际输出值是否与期望值吻合。

Mock测试

当单元测试涉及Web依赖时,需要使用Mock来保证单元测试的稳定性。

Mock 测试是在单元测试的基础上增加了模拟依赖关系的能力,因此可以在任何时间、任何环境执行,不依赖于本地文件,防止篡改。

Mock测试的特点

  • 模拟依赖关系
  • 独立测试
  • 控制函数返回值

例子

模拟数据库启动状态和数据库查询函数的返回值,测试数据库代码查询逻辑是否正确

package main

import (
    "fmt"
    "github.com/bouk/monkey"
    "database/sql"
)

//模拟数据库查询过程
func TertDBQuery(t *testing.T) {
    // 模拟数据库启动
    monkey.Patch(sql.Open, func(driverName, dataSourceName string) (*sql.DB, error) {
        return &sql.DB{}, nil
    })
    defer monkey.UnpatchAll()
    db, _ := sql.Open("mock", "")

    // 模拟数据库查询函数返回值
    monkey.PatchInstanceMethod(reflect.TypeOf(db), "Query", func(_ *sql.DB, _ context.Context, _ string, _ ...interface{}) (*sql.Rows, error) {
        return &sql.Rows{}, nil
    })
    defer monkey.UnpatchAll()

    rows, _ := db.Query("SELECT * FROM test")
    fmt.Println(rows)
}
  • Patch()函数是用于替换与需要测试的代码无关的函数、固定目标输出值,从而使得单元测试具有稳定性的函数。Patch()替换的是全局的函数
  • PatchInstanceMethod() 是用来替换某个实例对应的函数的,它的第一个参数是一个 reflect.Type 类型,表示这个实例的类型,第二个参数是字符串,表示需要被替换的函数名称,第三个参数是一个函数,表示用来替换的函数。
  • UnpatchAll() 函数是用来结束所有替换的恢复函数。Unpatch(函数名) 函数可以针对某个函数结束替换。

基准测试(BenchmarkTest)

基准测试是指用来测量代码执行性能的测试方法。

通过Go语言标准库testing包,基准测试可以用来测量函数的执行时间、内存占用等性能指标,分析代码性能的瓶颈。

例子

package main

import (
    "testing"
)

func BenchmarkAdd(b *testing.B) {
		//初始化计时器
		b.ResetTimer()
    for i := 0; i < b.N; i++ {
        add(1, 2)
    }
}

func add(a, b int) int {
    return a + b
}
  • b.ResetTimer的作用是重置计时器,只计算当前位置之后的代码执行性能,排除干扰
  • 基准测试函数接收一个*testing.B类型的参数,可以用来设置测试的迭代次数。在这个例子中,我们设置了迭代次数为b.N次,这样就可以测量add函数的执行时间了。

总结

测试是一个优秀项目不可或缺的一部分,特别是在一些频繁变动和多人合作开发的项目中尤为重要。你或多或少都会有因为自己的提交,导致应用挂掉或服务宕机的经历。如果这个时候你的修改导致测试用例失败,你再重新审视自己的修改,发现之前的修改还有一些特殊场景没有包含,恭喜你减少了一次上库失误。也会有这样的情况,项目很大,启动环境很复杂,你优化了一个函数的性能,或是添加了某个新的特性,如果部署在正式环境上之后再进行测试,成本太高。对于这种场景,几个小小的测试用例或许就能够覆盖大部分的测试场景。