モジュール 9 · レッスン 2 / 5コース内の 42/50~14 min
モジュールのレッスン (2/5)
テーブル駆動テスト
テーブル駆動テスト は Go の慣用的なパターンです。N 個の個別の TestThisSpecificCase 関数を記述する代わりに、ケースのスライスを宣言して反復します。アサーション ブロックは 1 つあり、テスト データはロジックから分離されています。
基本パターン
Go
func TestAbs(t *testing.T) {
cases := []struct {
name string
in int
want int
}{
{"pos", 3, 3},
{"neg", -3, 3},
{"zero", 0, 0},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := Abs(tc.in); got != tc.want {
t.Errorf("Abs(%d) = %d; voglio %d", tc.in, got, tc.want)
}
})
}
}3 つの成分:
- 通常のフィールドを含む 匿名構造体のスライス:
name、入力、予期される出力 (およびおそらく予期されるエラー)。 for _, tc := range cases— 実行ループ。t.Run(tc.name, func(t *testing.T) { ... })— すべてのケースは独自の名前を持つサブテストになります。
なぜ t.Run なのか?
t.Run は、以下を含む サブテストを作成します。
- 出力に表示される個人名:
TestAbs/pos、TestAbs/neg、... - 独自の
t *testing.T: あるケースで障害が発生しても、他のケースは停止しません (t.Fatalが使用されない限り)。 - コマンドライン フィルタリング:
go test -run TestAbs/negはその場合にのみ実行されます。 - オプションの並列処理: 関数内で
t.Parallel()を呼び出して並列化します。
Bash
go test -v -run TestAbs/neg予想されるエラーが発生するケース
Go
cases := []struct {
name string
in string
want int
wantErr bool
}{
{"ok", "42", 42, false},
{"bad", "x", 0, true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, err := Atoi(tc.in)
if (err != nil) != tc.wantErr {
t.Fatalf("err = %v, wantErr = %v", err, tc.wantErr)
}
if !tc.wantErr && got != tc.want {
t.Errorf("got %d, want %d", got, tc.want)
}
})
}ループ クロージャの落とし穴 (Go 1.22 以前)
Go 1.22 より前のバージョンでは、tc 変数は反復間で共有されていました。t.Run 内で t.Parallel() を呼び出すと、すべてのサブテストが同じ最後のケースに対して実行されました。修正内容は次のとおりです。
Go
for _, tc := range cases {
tc := tc // local rebind (pre-1.22)
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// ...
})
}ケースに名前を付ける
ベストプラクティス:
- 短いですが刺激的な名前:
"empty input"、"too large"、"negative"。 -runを使用して簡単にフィルタリングする場合はスペースを使用しないでください (またはアンダースコアを使用します)。- 「ハッピー パス」ケース、「エッジ」ケース (ゼロ、空)、および少なくとも 1 つのエラー ケースを常に含めます。
演習
運動#go.m9.l2.e1
試行回数: 0読み込み中…
in および want のフィールドを含む匿名構造体の case スライスを定義し、Abs (正、負、ゼロ) をテストする 3 つのケースを入力します。
エディターを読み込み中…
3 回の試行後に解決策が利用可能になります
運動#go.m9.l2.e2
試行回数: 0読み込み中…
t.Run with tc.name を追加して、各ケースを名前付きサブテストに変換します。
エディターを読み込み中…
3 回の試行後に解決策が利用可能になります
Quiz#go.m9.l2.e3
準備完了N 個の個別のテスト関数と比較したテーブル駆動パターンの主な利点は何ですか?
Go
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) { ... })
}