26.1 单元测试

开发中经常需要对一个包做(单元)测试,写一些可以频繁(每次更新后)执行的小块测试单元来检查代码的正确性,于是我们必须写一些 Go 源文件来测试代码。

使用testing包,我们只需要遵守简单的规则,就可以很好地写出通用的测试程序。因为其他开发人员也会遵循这个包的规则来进行测试。

首先测试程序是独立的文件,他必须属于被测试的包,和这个包的其他程序放在一起,并且文件名满足这种形式 *_test.go。由于是独立的测试文件,所以测试代码和包中的业务代码是分开的。Go语言这样规定的好处是不言而喻的,因为在其他语言开发的程序中,我们经常可以看到代码中注释掉的测试代码,而且有把开发版作为生产版发布到线上导致异常的问题出现。

当然,好的规则需要我们遵守并严格执行。

_test 程序不会被普通的 Go 编译器编译,所以当放应用部署到生产环境时它们不会被部署;只有 Gotest 会编译所有的程序:普通程序和测试程序。

测试文件中必须导入 "testing" 包,测试函数名字是以 TestXxx 打头的全局函数,Xxx部分可以为任意的字母数字的组合,但是首字母不能是小写字母[a-z],函数名我们可以以被测试函数的字母描述,如 TestFmtInterface,TestPayEmployees 等。测试用例会按照测试源代码中写的顺序依次执行。

测试函数一般都要求这种形式的头部:

func TestAbcde(t *testing.T)

*testing.T是传给测试函数的结构类型,用来管理测试状态,支持格式化测试日志,如 t.Log,t.Error,t.ErrorF 等。t.Log函数就像我们常常使用的fmt.Println一样,可以接受多个参数,方便输出调试结果。

用下面这些函数来通知测试失败:

  1. func (t *T) Fail() 标记测试函数为失败,然后继续执行剩下的测试。
  2. func (t *T) FailNow() 标记测试函数为失败并中止执行;文件中别的测试也被略过,继续执行下一个文件。
  3. func (t *T) Log(args ...interface{}) args 被用默认的格式格式化并打印到错误日志中。
  4. func (t *T) Fatal(args ...interface{}) 结合 先执行 3),然后执行 2)的效果。

运行 go test 来编译测试程序,并执行程序中所有的 TestXxx 函数。如果所有的测试都通过会打印出 PASS。

当然,对于包中不能导出的函数不能进行单元或者基准测试

gotest 可以接收一个或多个函数程序作为参数,并指定一些选项。go test 常用参数

  • -cpu: 指定测试的GOMAXPROCS值,默认是GOMAXPROCS当前值
  • -count: 运行单元测试和基准测试n次(默认1)。如设置了-cpu,则为每个GOMAXPROCS运行n次,示例函数总运行一次。
  • -cover: 启用覆盖率分析
  • -run: 执行功能测试函数,支持正则匹配,可以选择测试函数或者测试文件来仅测试单个函数或者单个文件
  • -bench: 执行基准测试函数,支持正则匹配
  • -benchtime: 基准测试最大时间上限
  • -parallel: 允许并行执行的最大测试数,默认情况下设置为GOMAXPROCS的值
  • -v: 展示测试过程信息

在系统标准包中,有很多 _test.go 结尾的程序,大家可以用来测试,为节约篇幅这里我就不写具体例子了。