Go工程实践之测试 | 青训营

116 阅读4分钟

测试分类

回归测试:质量保证人员。直接设置使用场景测试

集成测试:功能维度测试,通过服务暴露的接口,一般自动化

单元测试:单独的函数和模块验证,开发人员需要多注意这个。其实做法也就是多输入几组数据,看输出是否与期望符合。这样能很方便地定位小问题

从上到下,测试覆盖率变大,成本逐层降低

单元测试规则

  1. 所有测试文件以 _test.go 结尾,把测试文件和源文件放到一块儿方便使用
  2. func TestXxx(t *testing.T)
  3. 把单测放在一起:初始化逻辑放到 TestMain(m *testing.M) 中,做测试前的工作,比如说数据装载,配置初始化,之后 code := m.Run() 进具体的测试函数 跑所有的单测,测试后做释放资源等收尾工作,最后 os.Exit(code) 退出

以下是一个小测试案例

//main.go
func HelloTom() string {
    return "Jerry"
}

//main_test.go, 两个package一样, 位于同目录下
import (
    "testing"
    //先导入: go get package github.com/stretchr/testify
    "github.com/stretchr/testify/assert"
)

func TestHelloTom(t *testing.T) {
    output := HelloTom()
    expectOutput := "Tom"
 /* if output != expectOutput {
        t.Errorf("Expected %s do not match actual %s", expectOutput, output)
    }*/
    assert.Equal(t, expectOutput, output)
}

//运行测试: 可以直接在main_test.go里面点击测试图标, 也可以在这个目录位置运行 go test

单元测试的评估

代码覆盖率:可以在 go test 后面加上 --cover 选项,会显示 coverage 数据,其含义是,如果测试成功,那么这些用例通过的代码路径都是已经测试好了,在测试覆盖范围之内的,它占整个代码的比例就是代码覆盖率

这提醒我们,如果代码中有条件分支,两个分支都要有测试实例经过,为此,你可以写两个测试函数,比如说

//score > 60 ? true : false
func TestJudgePassLineTrue(t *testing.T){
    isPass := JudgePassLine(70)
    assert.Equal(t, true, isPass)
}

func TestJudgePassLineFail(t *testing.T){
    isPass := JudgePassLine(50)
    assert.Equal(t, false, isPass)
}
//go test --cover运行

如果代码测试覆盖率达到 100%,看上去好像也没那么严谨?但至少对正确性有点信心了。实际项目中,覆盖率一般有 50% - 60%,较高的覆盖率 80%(重要项目,比如支付)。一些提高覆盖率的注意事项:测试分支不重不漏,相互独立,全面覆盖,开发的时候函数写小一点,最好执行单一的值则,这样测试单元粒度可以足够小

外部依赖与测试

幂等(每次测试结果相同),稳定(测试能在任何时间任何函数独立执行),但因为程序会调用网络,数据库,文件系统之内的文件,之类的外部系统,外部的环境会导致测试结果不一致,此时需要使用 mock 机制

这里使用 monkey 包进行 mock 测试,go get package github.com/bouk/monkey

打桩:在测试时,用一个打桩函数替代原函数运行(函数地址的替换),在测试后卸载这个打桩函数。一般来说,这个原函数就是在调用外部系统,返回值不稳定,而打桩函数可以返回一个稳定的值以供测试

func TestProcessFirstLineWithMock(t *testing.T) {
//打桩: Patch(原函数, 打桩函数-这里用了匿名函数)
//这样ProcessFirstLine调用ReadFirstLine时, 执行的其实是这个打桩函数
//卸载打桩: defer Unpatch(原函数)
        monkey.Patch(ReadFirstLine, func() string {
                return "Line110"
        })
        defer monkey.Unpatch(ReadFirstLine)
        Line := ProcessFirstLine()
        assert.Equal(t, "Line000", Line)
}

基准测试

测试程序运行性能和 cpu 损耗,这个也有内置框架,它遵循以下几点规则

  1. 测试函数以 Benchmark 开头,其后的第一个字母大写,函数参数为 *testing.B 。基准测试分为串行的压力测试和并行的压力测试
  2. 串行:先执行不需要测时间的代码(比如一些初始化工作),然后做定时器重置 b.ResetTimer,最后 for i b.N 循环,在循环中执行需要测性能的步骤
  3. 并行:前两步和上面一样,之后 b.RunParallel( pb *testing.PB),for + pb.Next,在这个循环中执行需要测性能的步骤。并行不一定比串行快哦,如果并行代码有临界资源要抢锁的话性能可能会恶化的
//串行
func BenchmarkSelect(b *testing.B) {
    //不需要压测的部分
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        //压测部分
    }
}

//并行
func BenchmarkSelectParallel(b *testing.B) {
    //不需要压测的部分
    b.ResetTimer()
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            //压测部分
        }
    })
}

运行方法:可以点函数左边的开始测试按钮,也可以运行下面命令 go test -bench=. -benchmem。测试结果有如下字段

  1. 测试函数名 + GOMAXPROCS 值,默认为 CPU 核数
  2. 这个方法被执行了多少次
  3. 每次执行花费时间
  4. 每次执行申请多大的内存
  5. 每次执行申请几次内存 应用场景:比如测服务器负载均衡,就可以先随机选择,选中一个服务器执行,测整个过程的性能