Go 语言工程实践之测试(上) | 青训营笔记

80 阅读4分钟

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

本节重点

测试决定质量,质量决定稳定性。测试是实际开放过程中非常重要的一方面,本节主要学习了 Go 测试的一些相关内容,主要介绍了单元测试及其规则、覆盖率等。

不进行测试的后果

不进行测试,不经过精密完善的测试,会发生意外的事故,后果有时不堪设想,比如:

  1. 营销配置错误,导致非预期用户享受权益,资金损失 10w+

  2. 用户提现,幂等失效,短时间可以多次提现,资金损失 20w+

  3. 代码逻辑错误,广告位被占,无法出广告,收入损失 500w+

  4. 代码指针使用错误,导致 APP 不可用,损失上 kw+

不进行测试造成的事故非常多,后果也是非常严重的,测试是避免事故的最后一道屏障,可见其重要性不言而喻。

测试的类型

测试大致分为三类:第一个是回归测试,第二个是集成测试,是对系统功能维度做测试验证,第三个是单元测试。

image.png

从上到下,覆盖率逐层变大,测试成本却逐渐降低。所以说单元测试的覆盖率在一定程度上决定了代码的质量。

单元测试的构成

那么单元测试主要包括哪些部分呢?

image.png

如上图所示,其实其主要包括输入、测试单元、输出以及与期望的校对。这里的测试单元指得稍微宽泛一些,包含接口、一些函数模块或者说一切聚合的比较大的函数。最后是通过将输出与期望值进行校对,来反应当前代码输出是否与预期相同,从而验证代码的正确性。

那么通过一个一个的单元测试,就能够保证程序的质量,这个保证质量的意思是,在代码的整体覆盖率足够的情况下,保证了新功能本身的正确性,同时也表明了新代码没有破坏原代码的正确性,这样就是一个保证质量的概念。

另一方面,单元测试也能够提升效率,如当代码存在 bug 的情况下,其实可以通过本地直接运行,在一个较短的周期内,可能就可以直接定位问题,但是如果走编译,部署发布的形式,那么定位一个问题的周期会很长,可能会造成损失扩大化,所以写单元测试是很有必要的!

单元测试的规则

  1. 所有的测试文件都是以 _test.go 结尾。这样我们就能通过展开一个目录,就能清晰看到哪些是项目的源代码,哪些是测试代码,能够比较好地区分,并分离源代码与测试代码。
  2. 命名规范:
func TestXxx(*testing.T) 

命名存在问题的话,函数左边可能不会出现运行标志。

  1. 初始化逻辑放到 TestMain 中。函数内可以在测试前进行数据装载、配置初始化等工作,在测试后进行释放资源等收尾工作。

单元测试 - assert

刚刚使用的是不等于来测试是否正确,其实有很多开源的 assert 包能够帮我们实现很多 equal 或者 unequal 的操作。

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

func TestHelloTom(t *testing.T) {
    output := HelloTom()
    expectOutput := "Tom"
    assert.Equal(t, expectOutput, Output)
}

如上代码,使用的就是 assert 包中的 Equal 函数,这样就验证了 HelloTom 这个函数的正确性。

单元测试 - 覆盖率

那么如何衡量代码是否经过了足够的测试?

如何评价项目的测试水准?

如何评估项目是否达到了高水准测试等级?

代码覆盖率 can do it!代码覆盖率在一定程度上表明测试用例的覆盖度,越完备则代码正确性越高。

实际使用时,可以在用:

go test ...... --cover

后面加 cover 参数就可以,在运行 test 同时计算出测试代码的覆盖率。

其中的覆盖率,计算的是整个源代码中已经被执行的代码比例。比如代码有 3 行,测试时运行了 2 行,则覆盖率为 66.7%。

提高覆盖率:写多个 test 函数,不同情况都考虑到。

单元测试 - tips

  1. 一般覆盖率:50% ~ 60%,较高覆盖率 80%+。
  2. 测试分支相互独立、全面覆盖。
  3. 测试单元粒度足够小,函数单一职责。