Go Wiki:表驱动测试

介绍

编写良好的测试并非易事,但在许多情况下,表驱动测试可以覆盖很多内容:每个表条目都是一个完整的测试用例,包括输入和预期结果,有时还会包含其他信息,例如测试名称,以使测试输出易于阅读。如果您发现自己在编写测试时使用了复制和粘贴,请考虑是否将代码重构为表驱动测试或将复制的代码提取到辅助函数中,这可能是一个更好的选择。

给定一个测试用例表,实际测试只需遍历所有表条目,并对每个条目执行必要的测试。测试代码只编写一次,并在所有表条目上摊销,因此编写一个具有良好错误消息的仔细测试是有意义的。

表驱动测试不是工具、包或其他任何东西,它只是一种编写更清晰测试的方式和视角。

表驱动测试的示例

这是一个来自 fmt 包测试代码的很好的示例( https://pkg.go.dev/fmt/

var flagtests = []struct {
    in  string
    out string
}{
    {"%a", "[%a]"},
    {"%-a", "[%-a]"},
    {"%+a", "[%+a]"},
    {"%#a", "[%#a]"},
    {"% a", "[% a]"},
    {"%0a", "[%0a]"},
    {"%1.2a", "[%1.2a]"},
    {"%-1.2a", "[%-1.2a]"},
    {"%+1.2a", "[%+1.2a]"},
    {"%-+1.2a", "[%+-1.2a]"},
    {"%-+1.2abc", "[%+-1.2a]bc"},
    {"%-1.2abc", "[%-1.2a]bc"},
}
func TestFlagParser(t *testing.T) {
    var flagprinter flagPrinter
    for _, tt := range flagtests {
        t.Run(tt.in, func(t *testing.T) {
            s := Sprintf(tt.in, &flagprinter)
            if s != tt.out {
                t.Errorf("got %q, want %q", s, tt.out)
            }
        })
    }
}

请注意 t.Errorf 提供的详细错误消息:提供了其结果和预期结果;输入是子测试名称。当测试失败时,即使不阅读测试代码,也可以立即清楚地知道哪个测试失败以及原因。

t.Errorf 调用不是断言。即使记录了错误,测试也会继续。例如,在测试具有整数输入的内容时,值得知道该函数是否对所有输入都失败,或者仅对奇数输入失败,或者对 2 的幂失败。

使用 Map 存储测试用例

在前面的示例中,测试用例存储在结构体切片中。它们也可以存储在映射中,这样做有几个优点。

tests := map[string]struct {
  input string
  result string
} {
  "empty string":  {
    input: "",
    result: "",
  },
  "one character": {
    input: "x",
    result: "x",
  },
  "one multi byte glyph": {
    input: "🎉",
    result: "🎉",
  },
  "string with multiple multi-byte glyphs": {
    input: "🥳🎉🐶",
    result: "🐶🎉🥳",
  },
}

for name, test := range tests {
  // test := test // NOTE: uncomment for Go < 1.22, see /doc/faq#closures_and_goroutines
  t.Run(name, func(t *testing.T) {
    t.Parallel()
    if got, expected := reverse(test.input), test.result; got != expected {
      t.Fatalf("reverse(%q) returned %q; expected %q", test.input, got, expected)
    }
  })
}

使用映射的一个优点是,每个测试的“名称”可以简单地是映射索引。

更重要的是,映射迭代顺序没有指定,甚至没有保证从一次迭代到下次迭代是相同的。这确保了每个测试都独立于其他测试,并且测试顺序不会影响结果。

并行测试

并行化表测试很简单,但需要精确才能避免错误。请仔细注意以下三个更改,尤其是 test 的重新声明

package main

import (
    "testing"
)

func TestTLog(t *testing.T) {
    t.Parallel() // marks TLog as capable of running in parallel with other tests
    tests := []struct {
        name string
    }{
        {"test 1"},
        {"test 2"},
        {"test 3"},
        {"test 4"},
    }
    for _, test := range tests {
    // test := test // NOTE: uncomment for Go < 1.22, see /doc/faq#closures_and_goroutines
        t.Run(test.name, func(t *testing.T) {
            t.Parallel() // marks each test case as capable of running in parallel with each other 
            t.Log(test.name)
        })
    }
}

参考


此内容是 Go Wiki 的一部分。