Go言語でのテーブル駆動テストで失敗するテストケースを見つける方法

Go言語では、テーブル駆動テストが良い慣習としてよく知られています。

行動とデータを分割するのは素晴らしいのですが、現在のGoのテストでは、失敗したテストケースを見つけるのが直感的ではありません。代わりに、失敗したテスト表明の行番号が出るだけで、これは役に立ちません。

下の例を見てください:

package eg

import "testing"

func TestExample(t *testing.T) {
    testcases := []struct {
        name string
        a, b int
        sum  int
    }{
        {"1+1", 1, 1, 99},
        {"2+2", 2, 2, 4},
        {"4+4", 4, 4, 8},
        // [長いコード行...]
        {"1024+1024", 1024, 1024, -1},
    }
    for _, testcase := range testcases {
        t.Run(testcase.name, func(t *testing.T) {
            if got, expected := testcase.a+testcase.b, testcase.sum; got != expected {
                t.Errorf("expected %d, got %d", expected, got)
            }
        })
    }
}

テストを実行すると、次のような出力が得られます:

--- FAIL: TestExample (0.00s)
    --- FAIL: TestExample/1+1 (0.00s)
        eg_test.go:100: 期待値99, 結果2
    --- FAIL: TestExample/1024+1024 (0.00s)
        eg_test.go:100: 期待値-1, 結果2048 # <-- これは表明の行番号であって、テストケースのものではありません

どのテストケースが失敗したかに関わらず同じ行番号ばかりです。実際には、テストケース自体が失敗した行番号を見たいものです。テスト名では失敗したテストケースが何かある程度教えてくれるかもしれませんが、十分ではありません。

この問題を解決するために、私は小さなヘルパー・ライブラリを作成しました:go-testutil/dataloc。

https://pkg.go.dev/github.com/motemen/go-testutil/dataloc

[#使用方法]使用方法

上記の例を使用して、コードを少し変更してみましょう:

@@ -1,6 +1,7 @@
package eg

import "testing"
+import "github.com/motemen/go-testutil/dataloc"

func TestExample(t *testing.T) {
       testcases := []struct {
@@ -96,7 +97,7 @@
       for _, testcase := range testcases {
               t.Run(testcase.name, func(t *testing.T) {
                       if got, expected := testcase.a+testcase.b, testcase.sum; got != expected {
-                              t.Errorf("expected %d, got %d", expected, got)
+                              t.Errorf("期待値%d, 結果%d, テストケース位置 %s", expected, got, dataloc.L(testcase.name))
                       }
               })
       }

...それを実行すると、次のような出力が得られます。これは_アサーションの位置ではなく、失敗したテストケースの_場所を示しています。

--- FAIL: TestExample (0.00s)
    --- FAIL: TestExample/1+1 (0.00s)
        eg_test.go:100: 期待値99, 結果2, テストケース位置 eg_test.go:12
    --- FAIL: TestExample/1024+1024 (0.00s)
        eg_test.go:100: 期待値-1, 結果2048, テストケース位置 eg_test.go:95

[#内部の仕組み]内部の仕組み

この魔法を実現するために、ライブラリはruntime.Callerを使用して呼び出し元のファイルと行番号を取得し、次にgo/astを使用してファイルを解析し、対応するテストケースの位置を見つけます。

  1. dataloc.L(testcase.name)の形をした呼び出しを見つける
  2. testcaseの起源であるfor ... range testcasesを見つける
  3. testcasesの定義を見つける
  4. 実行時の値testcase.name("1+1" など)のテストケースの名前を持つテストケースの定義を見つける。
    • テストケース名が静的に決定されていない場合は、特定できません。

楽しいテーブル駆動テストを!

こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/motemen/locating-failing-test-cases-in-table-driven-tests-in-go-77m