Go Wiki:TableDrivenTests

簡介

撰寫良好測試並非易事,但在許多情況下,使用表格驅動測試可以涵蓋許多面向:每個表格項目都是一個包含輸入和預期結果的完整測試案例,有時候還會包含其他資訊,例如測試名稱,以使測試結果易於閱讀。如果您在撰寫測試時發現自己使用複製和貼上的功能,可以考慮找出更好的方案,例如將其整理成表格驅動測試或將複製的程式碼拉成一個輔助函式。

給定一個測試案例表格,實際測試會依序執行所有表格項目,並針對每個項目執行必要的測試。測試程式碼只需要撰寫一次,並將其攤銷到所有表格項目前,因此撰寫一個具良好錯誤訊息的測試極為重要。

表格驅動測試不是一個工具、套件或其他任何東西,它只是一個觀念和撰寫更整潔測試的視角。

表格驅動測試範例

以下是 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 呼叫並非斷言。就算已經記錄錯誤,測試也會繼續進行。例如,在測試某個包含整數輸入的函數時,知道這個函數會在所有的輸入、只會在奇數輸入,或只會在二的冪輸入時失敗,都是有用的資訊。

使用 Map 來儲存測試案例

在前面的範例中,測試案例被儲存在 struct 組成的切片中。它們也可以被儲存在 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)
    }
  })
}

使用 map 的好處之一是,每個測試的「名稱」就可以直接使用 map 的索引。

更重要的是,map 的迭代順序並未指定,甚至無法保證每次迭代都是相同的順序。這可確保每個測試獨立於其他測試,而且執行順序不會影響結果。

平行測試

平行執行表格測試很簡單,但是必須仔細執行,才能避免錯誤。請特別注意以下三項變更,尤其是 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 的一部分。