一只肥羊的思考

Go 项目单元测试最佳实践

Go 语言项目中,单元测试是必不可少的,就像我们在这篇博文中介绍的那样,单元测试占所有测试代码的 70% 的比重才算合理,可见单元测试的重要性。

这篇博文中,主要记录了一些常见的 Go 语言单元测试的最佳实践,当然这是一个可能后续会不断丰富完善的文档。

Table Driven Test

table driven test [1][2] 是很多 Go 语言开发者所推崇的测试代码编写方式,Go 语言标准库的测试也是通过这个结构来撰写的,比如 time 库的测试。

整体来讲,table driven test 的基本结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var testcases = []struct {
in string
out string
}{
{"in1", "out1"},
{"in2", "out2"},
}
func TestAFunc(t *testing.T) {
for _, tt := range testcases {
s := A(tt.in)
if s != tt.out {
t.Errorf("got %q, want %q", s, tt.out)
}
}
}

其中可见我们通过匿名结构体构建了每一个测试用例的结构,一个输入 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 中。所以此时的最佳实践一般是再构建一个文件,名称为 _internal_test.go。此时这些测试代码的 package 是和这个package 位于同一个 package 中。

所以一个完整的测试代码目录结构应该如下

1
2
3
4
-- gofmt
|-- gofmt.go (package gofmt)
|-- gofmt_internal_test.go (package gofmt)
|-- gofmt_test.go (package gofmt_test)

TBD

  • Mock 技术
  • Http Server 测试
  • 并发测试

Reference

  1. https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go
  2. https://github.com/golang/go/wiki/TableDrivenTests
  3. https://speakerdeck.com/mitchellh/advanced-testing-with-go?slide=22
  4. https://medium.com/soon-london/testing-with-golden-files-in-go-7fccc71c43d3
  5. https://dave.cheney.net/2016/05/10/test-fixtures-in-go
  6. https://medium.com/@benbjohnson/structuring-tests-in-go-46ddee7a25c
  7. https://talks.golang.org/2014/testing.slide#15