mcpField
G
go-test-table-driven

go-test-table-driven

github.com/eduardo-slテスト・品質83169 回インストール2 週間前に更新
リポジトリ

go-test-table-driven について

Go 開発者やコーディングエージェント向けのテストスキルです。テーブル駆動テストの適用場面と、ケースやループを簡潔に保つ方法を説明し、並列実行や複雑なテーブルのリファクタリングも扱います。

主な機能

  1. 01テーブル駆動テストの適用判断
  2. 02テストケースを簡潔に保つ
  3. 03説明的なケース名の使用
  4. 04ループ処理の簡素化
  5. 05安全なケースの並列実行
  6. 06肥大化したテーブルのリファクタリング

活用シーン

  1. 01共通パターンでパーサーやバリデーターをテスト
  2. 02テーブルと明示的なサブテストを選択
  3. 03分岐や複雑な準備を含むテーブルを整理

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/eduardo-sl/go-agent-skills --skill go-test-table-driven

インストール後、Claude Code で/go-test-table-drivenを入力して読み込まれたか確認します。

同梱ファイルreferences/patterns.mdreferences/refactoring.md

詳細

Goのテーブル駆動テスト

テーブル駆動テストは、正しく使えばGoの強力なイディオムです。多くのコードベースでは、活用が不十分(テストを10個コピー&ペースト)か、過剰に活用されています(200行の構造体に複雑な分岐ロジックを詰め込む)。このスキルでは、ちょうどよい使い方を説明します。

コミュニティのデフォルトです。go-test-table-driven を明示的に上書きするプロジェクトのスキルまたはエージェント設定ルールがある場合は、そちらが優先されます。

必要に応じて読み込む詳細なリファレンス資料:

  • references/patterns.md — 完全な実例:標準的なテーブル、wantErr/wantErrIs、並列テーブル、マップベースのテーブル、エラーのみのテーブル、読みやすさを考慮した構造体の配置。
  • references/refactoring.md — 肥大化したテーブルの見分け方と、明示的なサブテストへの書き換え方。変更前後の例付きです。

以下の概要だけでは現在のタスクに不十分な場合に限り、リファレンスファイルを読んでください。

1. テーブル駆動テストが適している場合

次の条件がすべて当てはまる場合にのみ、テーブルを使用します。

  • すべてのケースで同じ関数をテストする
  • アサーションのパターンが同じ — 入力を渡し、出力を比較する
  • ケース間で異なるのはデータだけであり、セットアップや検証ロジックではない
  • ケースが3つ以上 — 3つ未満なら、明示的なテストのほうが明確です

標準的な用途:純粋関数、パーサー、バリデーター、フォーマッター。

func TestParseSize(t *testing.T) {
    tests := []struct {
        name    string
        input   string
        want    int64
        wantErr bool
    }{
        {name: "plain bytes", input: "1024", want: 1024},
        {name: "kilobytes suffix", input: "4KB", want: 4096},
        {name: "empty string", input: "", wantErr: true},
        {name: "negative size", input: "-1", wantErr: true},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := ParseSize(tt.input)
            if tt.wantErr {
                require.Error(t, err)
                return
            }
            require.NoError(t, err)
            assert.Equal(t, tt.want, got)
        })
    }
}

すべてのケースが同じ形をしており、ループ本体は数行で済み、ケースの追加は構造体リテラルを1つ追加するだけです。分岐も条件文もありません。

2. テーブル駆動テストを使わない場合

  • ケースごとのセットアップが複雑 — 構造体に setupMock/setupFunc 関数フィールドがある場合、テーブルが複雑さを隠しています。明示的なサブテストを書いてください。
  • ケースが3つ未満 — 構造体の定義のほうが、通常のテスト関数を2つ書くよりコード量が多くなります。
  • 複数の分岐経路がある — ループ本体の if tt.shouldError/if tt.wantRedirect は、それぞれの分岐が、同じ構造を共有しているかのように見せかけた別々のテストであることを意味します。分割してください。

それぞれの問題を修正する前後の例については、references/refactoring.md を参照してください。

3. 構造体の設計ルール

  1. 各フィールドは、少なくとも2つのケース間で値が異なる必要があります。 どのケースでも同じ値のフィールドはセットアップ用です。テーブルの外に移してください。
  2. name フィールドは、シナリオを説明する短い文にします。 "case1" や "success" ではなく、"returns error for negative amount" のようにします。
  3. 「エラーになるべきか」の判定には wantErr bool を使います。 — 最初に確認し、ループ本体では早期に return します。
  4. 呼び出し元が特定のエラーを判別する必要がある場合は、センチネルエラーと wantErrIs error を使います。 require.ErrorIs でアサートします。
  5. フィールドは5つ以下にします。 それ以上ある場合、シナリオがテーブルに適さないほど複雑です。別々のテスト関数に分割してください。

フィールドパターンの詳細な例は、references/patterns.md にあります。

4. ループ本体は単純にする

テーブルテストの目的は、すべてのケースで同じ実行ロジックを使うことです。ループ本体は約10行以内に収めます。関数呼び出し、エラーチェック、比較だけにしてください。条件文やケースごとのセットアップが増えてきたら、テーブルの有用性が失われています。明示的なサブテストにリファクタリングしてください。

5. 並列テーブルテスト

for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
        t.Parallel()
        got := Transform(tt.input)
        assert.Equal(t, tt.want, got)
    })
}
  • Go 1.22以降では、ループ変数は反復ごとにスコープが設定されるため、tt := tt によるキャプチャは不要です。Go 1.22未満では、引き続きキャプチャが必要です。
  • テスト対象の関数に副作用がなく、共有の可変状態もない場合に限り、t.Parallel() を使用してください。

6. 肥大化したテーブルのリファクタリング

症状修正
構造体にフィールドが8つ以上あるシナリオごとに複数のテスト関数に分割する
構造体に setupFunc フィールドがある明示的なセットアップを行う別々のサブテストに切り出す
ループ本体に if tt.shouldX があるそれぞれの分岐は別のテストです。分割してください
すべてのケースで同じフィールドが3つあるテーブルの外にある共通のセットアップに移す
ケースを追加するために他のすべてのケースを理解する必要があるテーブルの有用な寿命を超えて肥大化しています

判断フローチャート

  1. 関数は純粋ですか(入力 → 出力、副作用なし)? はい → テーブルテストが最適でしょう。2に進みます。 いいえ → まず明示的なサブテストを検討します。

  2. すべてのケースで、まったく同じアサーションパターンを共有していますか? はい → テーブルテストです。3に進みます。 いいえ → 明示的なサブテストです。

  3. 各ケースを5個以下の構造体フィールドで表現できますか? はい → テーブルテストです。 いいえ → シナリオごとに別のテスト関数に分けます。

  4. ループ本体は10行以下ですか? はい → 申し分ありません。 いいえ → テーブルに複雑さが隠れています。リファクタリングしてください。

検証チェックリスト

  1. テーブル構造体には、ケースごとに異なるフィールドだけがある
  2. すべてのケースに説明的な name フィールドがある
  3. ループ本体は分岐なしで10行以下である
  4. 構造体に setupFunc フィールドや mockFunc フィールドがない
  5. wantErr は単純な bool またはセンチネルであり、文字列との照合ではない
  6. ケースには正常系、エラー系、エッジケース(空、nil、ゼロ、最大値)が含まれる
  7. 名前付きサブテストにするため、各ケースを t.Run で囲む
  8. t.Parallel() は関数に副作用がない場合のみ使用する

コメント