Go语言精进之路:从新手到高手的编程思想、方法和技巧(2)
上QQ阅读APP看书,第一时间看更新

41.2 xUnit家族模式

在Java、Python、C#等主流编程语言中,测试代码的组织形式深受由极限编程倡导者Kent Beck和Erich Gamma建立的xUnit家族测试框架(如JUnit、PyUnit等)的影响。

使用了xUnit家族单元测试框架的典型测试代码组织形式(这里称为xUnit家族模式)如图41-1所示。

015-01

图41-1 xUnit家族单元测试代码组织形式

我们看到这种测试代码组织形式主要有测试套件(Test Suite)和测试用例(Test Case)两个层级。一个测试工程(Test Project)由若干个测试套件组成,而每个测试套件又包含多个测试用例。

在Go 1.7版本之前,使用Go原生工具和标准库是无法按照上述形式组织测试代码的。但Go 1.7中加入的对subtest的支持让我们在Go中也可以使用上面这种方式组织Go测试代码。还以上面标准库strings包的测试代码为例,这里将其部分测试代码的组织形式改造一下(代码较多,这里仅摘录能体现代码组织形式的必要代码):

// chapter8/sources/strings-test-demo/compare_test.go
package strings_test

...

func testCompare(t *testing.T) {
    ...
}

func testCompareIdenticalString(t *testing.T) {
    ...
}

func testCompareStrings(t *testing.T) {
    ...
}

func TestCompare(t *testing.T) {
    t.Run("Compare", testCompare)
    t.Run("CompareString", testCompareStrings)
    t.Run("CompareIdenticalString", testCompareIdenticalString)
}

// chapter8/sources/strings-test-demo/builder_test.go
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)
}

改造前后测试代码的组织结构对比如图41-2所示。

015-01

图41-2 strings测试代码组织形式对比

从图41-2中我们看到,改造后的名字形如TestXxx的测试函数对应着测试套件,一般针对被测包的一个导出函数或方法的所有测试都放入一个测试套件中。平铺模式下的测试函数TestXxx都改名为testXxx,并作为测试套件对应的测试函数内部的子测试(subtest)。上面的代码中,原先的TestBuilderString变为了testBuilderString。这样的一个子测试等价于一个测试用例。通过对比,我们看到,仅通过查看测试套件内的子测试(测试用例)即可全面了解到究竟对被测函数/方法进行了哪些测试。仅仅增加了一个层次,测试代码的组织就更加清晰了。

运行一下改造后的测试:

$go test -v .
=== RUN   TestBuilder
=== RUN   TestBuilder/TestBuilder
=== RUN   TestBuilder/TestBuilderString
=== RUN   TestBuilder/TestBuilderReset
=== RUN   TestBuilder/TestBuilderGrow
--- PASS: TestBuilder (0.00s)
    --- PASS: TestBuilder/TestBuilder (0.00s)
    --- PASS: TestBuilder/TestBuilderString (0.00s)
    --- PASS: TestBuilder/TestBuilderReset (0.00s)
    --- PASS: TestBuilder/TestBuilderGrow (0.00s)
=== RUN   TestCompare
=== RUN   TestCompare/Compare
=== RUN   TestCompare/CompareString
=== RUN   TestCompare/CompareIdenticalString
--- PASS: TestCompare (0.44s)
    --- PASS: TestCompare/Compare (0.00s)
    --- PASS: TestCompare/CompareString (0.44s)
    --- PASS: TestCompare/CompareIdenticalString (0.00s)
PASS
ok         strings-test-demo     0.446s

我们看到go test的输出也更有层次感了,我们可以一眼看出对哪些函数/方法进行了测试、这些被测对象对应的测试套件以及套件中的每个测试用例。