【Go言語】テーブルドリブンテストの実装方法と活用例 〜実践的なテストコードの書き方〜

# 【Go言語】テーブルドリブンテストの実践的な実装方法と活用例

こんにちは！フリーランスエンジニアのこたろうです。

Go言語での開発において、効率的なテスト実装は非常に重要です。今回は、実際のプロジェクト例を用いながら、テーブルドリブンテストの実装方法とその活用について解説します。

## テーブルドリブンテストとは？

テーブルドリブンテストは、テストケースをデータとして管理し、反復的に処理することでコードを簡潔かつ明瞭に記述するテスト手法です。この手法を使うことで、テストケースの追加や修正が容易になり、コードの保守性も向上します。

## テストの基本的な流れ

テーブルドリブンテストは、以下の3つのステップで実装します：

1. テストケース名とテストデータのスライスを作成
2. for文でスライスをループ処理
3. サブテストの実行

## 実装例

今回は、記事の一覧・詳細を取得する簡易ブログシステムを例に説明します。

### 1. テストの前処理・後処理

```go
package repositories_test

import (
    "database/sql"
    "fmt"
    "os"
    "testing"

    _ "github.com/go-sql-driver/mysql"
)

var testDB *sql.DB

func TestMain(m *testing.M) {
    // 前処理
    if err := setup(); err != nil {
        fmt.Println("Failed to set up test environment:", err)
        os.Exit(1)
    }

    // テスト実行
    code := m.Run()

    // 後処理
    teardown()

    os.Exit(code)
}

func setup() error {
    dbUser := "hogeuser"
    dbPassword := "hogepass"
    dbDatabase := "hogedb"
    dbConn := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s?parseTime=true", 
        dbUser, dbPassword, dbDatabase)

    var err error
    testDB, err = sql.Open("mysql", dbConn)
    return err
}

func teardown() {
    testDB.Close()
}
```

### 2. 通常のテスト実装例

```go
func TestSelectArticleList(t *testing.T) {
    expectedNum := 3
    got, err := repositories.SelectArticleList(testDB, 1)
    if err != nil {
        t.Fatal(err)
    }

    if len(got) != expectedNum {
        t.Errorf("Expected %d articles, but got %d", expectedNum, len(got))
    }
}
```

### 3. テーブルドリブンテストの実装例

```go
func TestSelectArticleDetail(t *testing.T) {
    tests := []struct {
        testTitle string
        expected  models.Article
    }{
        {
            testTitle: "正常系：最初の投稿を取得",
            expected: models.Article{
                ID:       1,
                Title:    "firstPost",
                Contents: "This is my first blog",
                UserName: "ikeda",
                NiceNum:  3,
            },
        },
        {
            testTitle: "正常系：2番目の投稿を取得",
            expected: models.Article{
                ID:       2,
                Title:    "2nd",
                Contents: "Second blog post",
                UserName: "ikeda",
                NiceNum:  4,
            },
        },
    }

    for _, test := range tests {
        t.Run(test.testTitle, func(t *testing.T) {
            got, err := repositories.SelectArticleDetail(testDB, test.expected.ID)
            if err != nil {
                t.Fatal(err)
            }

            if got.ID != test.expected.ID {
                t.Errorf("Expected ID %d, but got %d", 
                    test.expected.ID, got.ID)
            }
            if got.Title != test.expected.Title {
                t.Errorf("Expected Title %s, but got %s", 
                    test.expected.Title, got.Title)
            }
            // その他のフィールドの検証も同様に実装
        })
    }
}
```

## 実装のポイント

1. **テストケースの構造体定義**
   - テストケースごとに期待値と入力値を明確に定義
   - テストケース名は具体的な内容を記述

2. **サブテストの活用**
   - `t.Run`を使用して各テストケースを独立して実行
   - テスト失敗時に該当するケースが特定しやすい

3. **エラーメッセージの充実**
   - 期待値と実際の値を明確に表示
   - どのフィールドで失敗したかを具体的に示す

## テストの実行方法

```bash
# 詳細な出力でテストを実行
go test -v

# 特定のテストのみを実行
go test -v -run TestSelectArticleDetail
```

実行結果の例：
```
=== RUN   TestSelectArticleDetail
=== RUN   TestSelectArticleDetail/正常系：最初の投稿を取得
--- PASS: TestSelectArticleDetail/正常系：最初の投稿を取得 (0.01s)
=== RUN   TestSelectArticleDetail/正常系：2番目の投稿を取得
--- PASS: TestSelectArticleDetail/正常系：2番目の投稿を取得 (0.01s)
PASS
```

## まとめ

テーブルドリブンテストのメリット：

- テストケースの追加・修正が容易
- コードの重複を防ぎ、保守性が向上
- テスト結果が明確で理解しやすい
- 複数のパターンを効率的にテスト可能

この手法を活用することで、より堅牢なテストを効率的に実装することができます。ぜひ、みなさんのプロジェクトでも取り入れてみてください！

## 参考文献

- 『Goで作るはじめてのWebアプリケーション 改訂版』技術書典
  - [https://techbookfest.org/product/jXDAEU1dR53kbZkgtDm9zx](https://techbookfest.org/product/jXDAEU1dR53kbZkgtDm9zx)