go语言中单元测试

二维码
| Aug 13, 2020 | 原创

Go 语言里做单元测试和基准测试可以说是超级简单,因为 Go 自带测试包(testing),不在需要引入第三方测试框架,减少了学习成本。并且编写测试代码非常容易,这也是笔者比较青睐 Go 语言的地方,想要编写测试代码,你只需要创建一个以 _test.go 结尾的文件即可。

一般的我们会在需要测试的函数的同文件目录创建一个对应的测试文件,如下,如果我们想测试 send.go 文件里的函数方法,我们会在同级目录下新建一个 send_test.go 文件:

utils
├── crypto.go
└── crypto_test.go

这些以 _test.go 结尾的文件是不会参与最终 go build 编译阶段的,因此你不用担心这些文件是否会被编译发布到线上环境。

单元测试

做单元测试,是一个非常好的编码习惯,尤其是在多人协作开发时,经常会遇到 A 同学编写一个 公共函数供 B 同学使用的场景,如果在交付前做些单元测试,保证提供的函数按照预期运行,虽然写测试用例会花费一定时间,但是它保证了代码的质量,整体上来说减少了后期测试成本,以及沟通成本。

这里我们基于上述的 crypto.go 编写单元测试示例,在这个文件中,我们封装了两个工具函数: MD5()SHA256() 函数:

package crypto

// md5 hash
func MD5(text string) string {
    hashed := md5.New()
    hashed.Write([]byte(text))
    return hex.EncodeToString(hashed.Sum(nil))
}

// sha256 hash
func SHA256(text string) string {
    hashed := sha256.New()
    hashed.Write([]byte(text))
    return hex.EncodeToString(hashed.Sum(nil))
}

现在我们要为这俩函数编写单元测试函数,在同目录下创建一个: crypto_test.go 测试文件,并且编写单元测试函数。

单元测试函数要求函数命名以 Test 作为函数前缀,并且函数形参为 *testing.T 指针类型:

package crypto

func TestMD5(t *testing.T) {
    // 测试代码
}

func TestSHA256(t *testing.T) {
    // 测试代码
}

通常,我们需要测试某个函数,会把这个函数名再加上 Test 前缀作为测试函数名称,如上的 TestMD5() 函数,但这并非是硬性要求,你完全可以取一个 TestMd5Encode() 之类的函数,但是这肯定的不是个好建议。

如果你是使用 IDE 编写代码,那么写测试函数更加如鱼得水,当你敲击函数 T 字母时,自动补全功能会自动提示你需要创建的测试函数,是不是很简单:

在函数里,你可以编写相关测试逻辑:

package crypto

func TestMD5(t *testing.T) {
    fmt.Println(MD5("hello"))
    fmt.Println(MD5("the world"))
}

运行结果

Go 提供了一系列的测试命令执行这些测试函数,命令都是以 go test 开头,以下列举了常用的使用场景:

1.查看帮助:

go help test

查看 test 命令帮助文档。

2.go test 和 go test -v

go test

如果 test 后面不带任何参数, go test 会编译当前目录下所有包和测试代码,然后运行测试结果。在这种模式下, caching 是禁用的。在包测试完成后, go test 打印一个概要行,显示测试状态、包名和运行时间。

示例如下:

func TestMD5(t *testing.T) {
    fmt.Println(MD5("hello"))
}

func TestSHA256(t *testing.T) {
    fmt.Println(SHA256("hello"))
}

控制台输出:

➜  crypto git:(master) ✗ go test
5d41402abc4b2a76b9719d911017c592
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
PASS
ok  	ucenter/app/utils/crypto	1.310s

go test -vgo test 一致, -v 参数会输出更多测测试信息,例如测试函数名等:

➜  crypto git:(master) ✗ go test -v
=== RUN   TestMD5
5d41402abc4b2a76b9719d911017c592
--- PASS: TestMD5 (0.00s)
=== RUN   TestSHA256
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
--- PASS: TestSHA256 (0.00s)
PASS
ok  	ucenter/app/utils/crypto	4.919s

3. go test [文件名]

测试单个文件的测试函数

go test -v crypto_test.go

不过请注意此种方式的使用,有可能您在使用的时,运行结果会报错:

➜  crypto git:(master) ✗ go test -v crypto_test.go
# command-line-arguments [command-line-arguments.test]
./crypto_test.go:9:14: undefined: MD5
./crypto_test.go:13:14: undefined: SHA256
FAIL	command-line-arguments [build failed]
FAIL

错误提示说, MD5 函数未定义,我们可以看下我们编写这个测试函数的方式:

fmt.Println(MD5("hello"))

我们使用这个 MD5 函数并不是以 import 包名 方式使用的,而是直接使用,我们认为的是这个函数所在的文件 crypto.go 和测试文件 crypto_test.go 都在 crypto 包目录下,他们的包名目前也是一样的,因此不需要使用 import 便可以调用多个文件的函数方法。

这么理解是没有问题的,但是问题在于,当我们使用 go test crypto_test.go 规定了只会编译 crypto_test.go 文件,而不会编译同目录的 crypto.go 文件导致的。

知道原因所在,那么如果解决呢?解决方式有两种:

一种是在测试时同时制定依赖的文件 crypto.go:

go test -v crypto.go crypto_test.go

另一种方式,我们调整 crypto_test.go 文件 package 名称为: package_test 然后,使用 import 导出方式使用这些函数:

package crypto_test

import (
	"fmt"
	"testing"
	"ucenter/app/utils/crypto"
)

func TestMD5(t *testing.T) {
	fmt.Println(crypto.MD5("hello"))
}

func TestSHA256(t *testing.T) {
	fmt.Println(crypto.SHA256("hello"))
}

然后再运行 go test -v crypto_test.go 文件也不会出现任何问题。不过这种方式相比第一种方式增加了不少代码量,另外由于 package 不同会导致如果想在 test 文件里使用 crypto.go 文件里点的私有函数就行不通了。

4. go test -run=[regex]

测试命令提供了一个 -run 参数其后可以指定一个正则表达式,这样运行会去查找匹配到的测试函数,例如,我们只想测试 MD5 方法:

go test -v -run=TestMD5

除了这些使用方式外,还有其他一些参数可以配置,如:测试并发数,超时、传递参数,覆盖率等,详细内容请参考帮助文档。

上面的运行命令都是基于命令行的方式,还可以使用 IDE 运行测试更为方便,如测试某个函数,选择需要测试的函数左侧的测试箭头 → 单击 → 运行即可:

总结

本节给大家分享了 Go 语言如何编写单元测试,已经常用命名使用方式介绍,下一节我们将继续分享 Go 另一个测试话题:基准测试,如果使用基准测试来测试代码的性能。