スキップしてメイン コンテンツに移動

Gin初心者のテスト入門

Gin初心者のテスト入門

Gin初心者のためのテスト入門

GoでWebアプリを構築する際に人気のフレームワーク、Ginは高速でシンプルな設計が特徴です。Gin初心者がまず取り組むべきは、テストの基本を理解することです。テストコードを書くことで、品質保証の観点からバグを早期に発見し、リリース後の修正コストを削減できます。

まずは、Ginのハンドラを単体で検証するユニットテストを作成します。テスト対象の関数を分離し、入力と期待される出力を明確に定義することで、テストケースを簡潔に保つことができます。

func TestHelloHandler(t *testing.T) {
    // Arrange
    req := httptest.NewRequest(http.MethodGet, "/hello", nil)
    w := httptest.NewRecorder()

    // Act
    gin.Default().GET("/hello", HelloHandler).ServeHTTP(w, req)

    // Assert
    if w.Code != http.StatusOK {
        t.Errorf("expected status 200, got %d", w.Code)
    }
}

上記の例では、httptestパッケージを使ってリクエストとレスポンスをモックしています。これにより、実際のネットワークを介さずにハンドラの挙動を検証できます。

httptestで作るテストケース

Ginのルーティングやミドルウェアをテストする際には、httptestを活用して実際にHTTPリクエストを送信し、レスポンスを検証します。テストケースを作成する際のポイントは、入力データと期待結果を明確に定義し、再現性のあるテストを構築することです。

  • リクエストパラメータやクエリ文字列を変化させる。
  • 認証情報を付与したリクエストを送信し、認可ロジックを検証。
  • エラーハンドリングを確認し、適切なステータスコードとメッセージが返るか確認。

以下は、複数のテストケースをまとめた例です。

func TestAuthMiddleware(t *testing.T) {
    tests := []struct {
        name       string
        token      string
        wantStatus int
    }{
        {"valid token", "valid-token", http.StatusOK},
        {"missing token", "", http.StatusUnauthorized},
        {"invalid token", "bad-token", http.StatusUnauthorized},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            req := httptest.NewRequest(http.MethodGet, "/secure", nil)
            if tt.token != "" {
                req.Header.Set("Authorization", "Bearer "+tt.token)
            }
            w := httptest.NewRecorder()
            gin.Default().GET("/secure", AuthMiddleware(), SecureHandler).ServeHTTP(w, req)

            if w.Code != tt.wantStatus {
                t.Errorf("expected %d, got %d", tt.wantStatus, w.Code)
            }
        })
    }
}

このようにテストケースをテーブル駆動で書くことで、コードの重複を減らし、テストの可読性を向上させます。

モックを使ったユニットテストと統合テスト

外部サービスやデータベースに依存するロジックをテストする場合、モックを導入することでテストの高速化と安定化が図れます。Goでは、インターフェースを定義し、テスト時にモック実装を注入する手法が一般的です。

以下は、リポジトリインターフェースをモック化してユニットテストを行う例です。

type UserRepository interface {
    FindByID(ctx context.Context, id int) (*User, error)
}

type MockUserRepo struct {
    Users map[int]*User
}

func (m *MockUserRepo) FindByID(ctx context.Context, id int) (*User, error) {
    user, ok := m.Users[id]
    if !ok {
        return nil, errors.New("not found")
    }
    return user, nil
}

func TestGetUser(t *testing.T) {
    mockRepo := &MockUserRepo{Users: map[int]*User{1: {ID:1, Name:"Alice"}}}
    svc := NewUserService(mockRepo)

    user, err := svc.GetUser(context.Background(), 1)
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    if user.Name != "Alice" {
        t.Errorf("expected Alice, got %s", user.Name)
    }
}

モックを使うことで、実際のデータベース接続を待つ必要がなくなり、テスト実行時間を短縮できます。また、統合テストでは、モックを使わずに実際のサービスを起動し、全体のフローを検証します。統合テストは、品質保証の観点から重要であり、CIパイプラインに組み込むことでリリース前に大きな問題を発見できます。

まとめとして、Gin初心者はまず単純なユニットテストから始め、httptestで実際のHTTPリクエストを模倣し、モックを活用して外部依存を排除します。テストコードを継続的に書き、品質保証を徹底することで、堅牢で保守性の高いWebアプリケーションを構築できます。

この記事はAIによって作成されました。

コメント