Go 语言项目中,单元测试是必不可少的,就像我们在这篇博文中介绍的那样,单元测试占所有测试代码的 70% 的比重才算合理,可见单元测试的重要性。
这篇博文中,主要记录了一些常见的 Go 语言单元测试的最佳实践,当然这是一个可能后续会不断丰富完善的文档。
Table Driven Test
table driven test [1][2] 是很多 Go 语言开发者所推崇的测试代码编写方式,Go 语言标准库的测试也是通过这个结构来撰写的,比如 time 库的测试。
整体来讲,table driven test 的基本结构如下:
|
|
其中可见我们通过匿名结构体构建了每一个测试用例的结构,一个输入 in 和一个我们期望的输出 out,然后在真实的测试函数中,通过 range 轮询每一个测试用例,并且调用测试函数,比较输出结果,如果输出结果不等于我们期望的结果,即报错。
这种测试框架最好的一点在于,结构清晰,并且添加新的测试 case 会非常方便,所以也是最为推荐的一种测试的编写方式。
Test Fixtures, Golden Files
在某些场景下,我们的测试可能需要一些除了测试源码以外的额外的文件,比如我们在测试对 json 文件的解码功能时,就需要一些示例的 json 文件作为测试 case 的输入。
像这种场景,把这些测试过程中用到的辅助文件,通常就叫做 test fixtures[3][4][5]。而放置这些文件最佳的路径,就是测试代码所在路径的一个名字为 testdata 的子目录下面,原因有二
- 测试代码运行时,他的 working dir 就是测试代码的当前路径
- go 语言在编译时会忽略名字叫做 testdata 的子目录
所以二者结合,我们应该把这些文件放在这样的路径下
而 Golden Files 又是什么呢?Golden Files 其实就是 test fixtures 中的一种,当测试用例的输出结果比较简单的时候,我们还可以把输出结果写在测试代码中 。但是当输出结果比较复杂时,直接写入代码已经不太合适了。所以此时,我们通常会把正确结果写入到文件里面,并且测试代码运行时,需要读取这个文件的内容进行比较。
一般 Golden File 的使用都会配合 Table Driven Test,每一个测试 case 的 Golden File 的名字一般就会以“这个 case 的名字+.golden” 来命名,这样在编写代码时也会比较简单。
标准库就包含这种使用方法的很多实例,比如对 gofmt 的测试 case:https://golang.org/src/cmd/gofmt/gofmt_test.go
*_test 包
单元测试文件一般被放置在待测试文件的同级目录下,而且文件名称基本和待测试文件名称加上”_test.go” 的规则来进行命名。
那么单测代码应该放在那个 package 中呢?一般没有特殊的需求的话,单元测试应该仅仅关注待测试的 package 的 exported 方法的测试。因为这些 exported 方法是这个 package 对外进行交互的唯一接口,所以必须要保证它的一致性。
所以一般也会将测试代码放置到另一个 package 中,假设待测试的 package 名称叫做 fmt,那么它的单测代码一般所在的包名为 fmt_test。
但是如果你真的需要对一些逻辑比较复杂的非 exported 函数进行测试的话,测试代码一定必须要和待测试的 package 在同一个 package 中。所以此时的最佳实践一般是再构建一个文件,名称为
所以一个完整的测试代码目录结构应该如下
|
|
TBD
- Mock 技术
- Http Server 测试
- 并发测试
Reference
- https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go
- https://github.com/golang/go/wiki/TableDrivenTests
- https://speakerdeck.com/mitchellh/advanced-testing-with-go?slide=22
- https://medium.com/soon-london/testing-with-golden-files-in-go-7fccc71c43d3
- https://dave.cheney.net/2016/05/10/test-fixtures-in-go
- https://medium.com/@benbjohnson/structuring-tests-in-go-46ddee7a25c
- https://talks.golang.org/2014/testing.slide#15