Golang 如何进行单元测试
在 golang 中,如果有一个 practice.go 的文件,那么在后面加上 _test(practice_test.go) 就是对应的单元测试文件。当构建程序时,名为 xxx_test.go 的文件会被编译器忽略。一般来说,单元测试的文件和被测试的文件放在同一个目录下,单元测试文件也是 package 的一部分。
第一个单元测试
1 | package greeting |
对应的测试代码如下:
1 | package greeting |
单元测试的方法签名分为两部分,第一部分是固定的 Test,第二部分则是被测试的方法名,且必须是大写字母开头。
单元测试没有标记成功的方法,当测试方法没有调用任何失败方法时,就意味着测试成功。要标记失败,有如下几种方式:
Error
,记录日志,标记单元测试方法失败,测试将继续Errorf
,同上,日志按照指定格式记录Fail
,标记单元测试方法失败,测试将继续FailNow
,标记测试失败,将不会继续执行Fatal
,同FailNow
+ 记录日志Fatalf
,同FailNow
+ 指定格式记录日志
如果需要使用额外的文件,如配置文件等,将其放在当前目录的 testdata
文件夹下。
以上的单元测试使用的是标准库的方法,也可以引入开源库来做断言处理。
1 | go getgithub.com/stretchr/testify |
上面的测试代码就可以改写成:
1 | package greeting |
执行单元测试
只需 cd 到对应文件的目录下,执行go test
即可执行单元测试,这个命令会执行当前目录下 package 中的所有测试用例。如果想要执行整个项目中的所有测试用例,可以使用 go test ./...
。
在 go test
执行的过程中,go 也会在 package 中自动执行 go vet
。go vet
命令是 Go 工具链的一部分。它可以对源代码进行语法验证,以检测潜在的错误。这个命令有一个完整的检查列表,但是在运行 go 测试时,只启动一个小的检测列表子集。
- atomic:检测出 sync/atomic 包的错误使用。
- bool:验证布尔条件的使用情况。
- buildtags:验证在输入的命令中
go test
是否正确拼装了 build 标签。 - nilfunc:检查是否将函数与nil进行比较。
在启动单元测试之前自动运行go vet
命令可以使你在对程序造成影响之前就发现这些错误。
多个测试用例
如果一个方法有多个测试用例需要去测试,那么该如何去写呢?最容易想到的肯定是直接复制然后修改。其实可以把测试用例参数化,具体来看如下代码:
1 |
|
首先,创建一个名为 args
的 struct
。每一个字段都代表待测试方法中的参数。
然后再创建一个 tests
的 struct
,这里有三个字段:
- name,即该测试用例的名字,一般取个比较易读易理解的名字。
- args,传递给被测试方法的参数。
- want,期望执行方法的返回值。
接下来的循环中,则是遍历执行测试用例了。这样添加或删除测试用例就方便多了。
并行测试
如果项目中测试用例太多或者耗时太长,就需要引入并行测试了。
新添加三个测试方法,每个方法不做其它事情,只是单纯地 sleep 500毫秒。
1 | func TestGreeting1(t *testing.T) { |
执行测试,终端输出:
1 | PASS |
可以看到,耗时是相当长的。此时只需要在每个测试方法里面加上t.Parallel()
,再次运行:
1 | PASS |
测试时间成功变少了,而仅仅只是添加了一句代码而已。
测试覆盖率
go test
添加 -cover
参数可以查看测试覆盖率,也可以通过 go test -coverprofile profile
生成报告。报告内容如下所示:
1 | mode: set |
这个文件并不易读。第一行先忽略,从第二行开始看。首先是文件路径,5.35代表了测试块的起始行列,7.2代表了测试块的结束行列。接下来的两个数字分别表示总的语句数量和测试覆盖到的语句数量。
由于此文件并不清晰易读,可以使用 go tool cover -html=profile
生成一个 html 文件并在浏览器中打开。