lifelong learning & practice makes perfect
《Go语言精进之路:从新手到高手》在”测试”这一主题相关的部分章节, 作者从测试文件的位置(包内/包外测试)讲起,介绍了测试代码的层次结构,常见测试模式,表驱动测试和 go对测试数据组织做的优化,以及mock测试等辅助单元测试,fuzz测试
包内测试:测试代码与被测代码在同一包内
#包外测试
go list -f= .
#在$GoROOT/src/strings目录下为 [builder_test.go clone_test.go compare_test.go example_test.go reader_test.go replace_test.go search_test.go strings_test.go]
#包内测试,在$GoROOT/src/strings目录下为 [export_test.go]
go list -f= .
仅针对导出API进行测试,是面向接口的黑盒测试.
针对包外测试对被测代码覆盖不足的情况,可以使用”安插后门”的方式,即在测试文件(*_test.go)提供代码用于测试
在测试文件中的代码不对包含在正式产品代码中.
利用go在1.14后提供的testing.CleanUp,实现测试固件的销毁,TestMain实现包级别的测试固件创建和销毁.
参考testsuite下的测试案例
在 go中也能实现类似Java,Pyhton等其他语言里主流的xUnit类似的测试套件(Test Suite)
package strings_test
func testBuilder(t *testing.T) {
...
}
func testBuilderString(t *testing.T) {
...
}
func testBuilderReset(t *testing.T) {
...
}
func testBuilderGrow(t *testing.T) {
...
}
func TestBuilder(t *testing.T) {
t.Run("TestBuilder", testBuilder)
t.Run("TestBuilderString", testBuilderString)
t.Run("TestBuilderReset", testBuilderReset)
t.Run("TestBuilderGrow", testBuilderGrow)
}
基于数据的测试
func TestCompare(t *testing.T) {
compareTests := []struct {
name, a, b string
i int
}{
{`compareTwoEmptyString`, "", "", 0},
{`compareSecondParamIsEmpty`, "a", "", 1},
{`compareFirstParamIsEmpty`, "", "a", -1},
}
for _, tt := range compareTests {
t.Run(tt.name, func(t *testing.T) {
cmp := strings.Compare(tt.a, tt.b)
if cmp != tt.i {
t.Errorf(`want %v, but Compare(%q, %q) = %v`, tt.i, tt.a, tt.b, cmp)
}
})
}
}
还可以单独执行某项数据的测试
$go test -v -run /TwoEmptyString ./base/tests/table_driven_test.go
=== RUN TestCompare
=== RUN TestCompare/compareTwoEmptyString
--- PASS: TestCompare (0.00s)
--- PASS: TestCompare/compareTwoEmptyString (0.00s)
PASS
ok command-line-arguments 0.005s
go工具链默认忽略testdata目录.
将预期数据采集到文件的过程与测试代码结合
fake,stub,mock概念,区分可参考xUnit Test Patterns
使用真实主键或简化版本作为被测代码的外部依赖,如测试里常用的redis包 github.com/alicebob/miniredis/v2,内存中运行的redis
mock替身更为强大,除了能提供测试前的预设置返回结果能力之外,还可以对mock替身对象在测试过程中的行为进行观察和验证
gomock,支持用mockgen生成代码(用于mock interface,即接口) mockgen支持source/reflect两种方式生成mock代码