2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ORMを使ったテストをどう書くか

Posted at

ORMを使ったテストコードをどう書くべきか、悩んだことはないだろうか。

本記事ではGo + GORM + PostgreSQLの構成で、テストのスピードと正確性を両立する戦略について紹介する。

プロジェクト構成

今回のプロジェクト構成は以下の通りである。

  • ORM: GORM
  • マイグレーション: Goose
  • DB: PostgreSQL
  • 目的: Webメディアサービス開発

テストで重視したいこと

テストには相反する要求がある。

  • スピード: CIの実行時間を短くしたい
  • 正確性: 本番環境に近い環境でテストしたい
  • コスト: 複雑な環境構築は避けたい

これらをすべて満たすのは難しいが、使い分けることで両立できる。

テストツールの候補

検討したツールは以下の4つである。

1. go-sqlmock

SQLのモック化ライブラリである。

メリット:

  • 超高速(DB不要)
  • 環境構築不要
  • 発行されるクエリの検証が可能

デメリット:

  • 実際のDBを動かしていない
mock.ExpectQuery(
  "SELECT * FROM users WHERE id = $1",
).WithArgs(1).WillReturnRows(
  sqlmock.NewRows([]string{"id", "name"}).
    AddRow(1, "Alice"),
)

// テスト実行
user, err := repo.GetUser(ctx, 1)

2. testcontainer

Go製のDockerコンテナ管理ライブラリである。テスト実行時に自動でPostgresコンテナを起動できる。

メリット:

  • 本番環境と同じDBでテスト可能

デメリット:

  • Docker環境が必須
  • コンテナの起動に時間がかかる(数秒)
  • CIでの実行コストが高い
postgres, err := testcontainers.GenericContainer(ctx,
  testcontainers.GenericContainerRequest{
    ContainerRequest: testcontainers.ContainerRequest{
      Image: "postgres:15",
      ExposedPorts: []string{"5432/tcp"},
    },
    Started: true,
  })

3. Docker Compose

docker-compose.ymlでPostgresを起動し、開発者が手動でコンテナ管理する。

メリット:

  • 本番環境と同じDBでテスト可能

デメリット:

  • Docker環境が必須
  • CIでのセットアップが煩雑
services:
  postgres:
    image: postgres:15
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB: testdb
      POSTGRES_USER: test

4. SQLite (インメモリ)

インメモリDBで超高速である。ORMで抽象化されているから使えるかもしれない。

メリット:

  • 超高速(インメモリ)
  • 環境構築不要

デメリット:

  • PostgreSQLとSQLiteは別物
  • Gooseのマイグレーションが使えない
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
db.AutoMigrate(&User{}, &Article{})

本番環境との差異が大きすぎるため却下した。

ツール比較表

ツール スピード 正確性 Docker要否 CI適合
go-sqlmock 不要
testcontainer 必要
Docker Compose 必要
SQLite 不要

採用した戦略

単体テストと結合テストで使い分けることにした。

  • 単体テスト: スピード重視 → go-sqlmock
  • 結合テスト: 正確性重視 → Docker + Postgres

単体テスト: go-sqlmock

採用の根拠は以下の通りである。

  • 実行速度が最優先
  • CI時間を短縮したい
  • ビジネスロジックのテストに集中
  • 外部制約のためにセットアップするコストを排除

対象:

  • リポジトリ層
  • ビジネスロジック
  • 頻繁に実行するテスト

実行頻度:

  • 毎回のコミット
  • Pull Request時

実行時間は1秒以内である。

結合テスト: Docker + Gooseマイグレーション

採用の根拠は以下の通りである。

  • 本番環境と同等の状態でテスト
  • マイグレーションも含めてテスト
  • リレーションなど制約の動作を保証

対象:

  • E2Eフロー
  • マイグレーション検証
  • リレーションを含むSQL

実行頻度:

  • main / release/* branch へのmerge時
  • デプロイ前

実行時間は数秒〜数十秒である。

実装例

go-sqlmock

func TestGetUser(t *testing.T) {
    db, mock, err := sqlmock.New()
    require.NoError(t, err)
    defer db.Close()

    gormDB, err := gorm.Open(postgres.New(
        postgres.Config{Conn: db}), &gorm.Config{})

    // 期待するクエリを定義
    rows := sqlmock.NewRows([]string{"id", "name", "email"}).
        AddRow(1, "Alice", "alice@example.com")
    mock.ExpectQuery(`SELECT \* FROM "users"`).
        WithArgs(1).
        WillReturnRows(rows)

    // テスト実行
    repo := NewUserRepository(gormDB)
    user, err := repo.GetUser(ctx, 1)

    assert.NoError(t, err)
    assert.Equal(t, "Alice", user.Name)
}

結合テスト

//go:build integration

func TestE2E(t *testing.T) {
    dsn := os.Getenv("TEST_DATABASE_URL")
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    require.NoError(t, err)

    server := setupTestServer(t, db)
    defer server.Close()

    setupTestData(t, db)
    defer cleanupTestData(t, db)

    resp, err := http.Get(server.URL + "/users")
    require.NoError(t, err)
    defer resp.Body.Close()

    assert.Equal(t, http.StatusOK, resp.StatusCode)

    var user User
    json.NewDecoder(resp.Body).Decode(&user)
    assert.Equal(t, "Alice", user.Name)
}

実行方法は以下の通りである。

docker-compose up -d postgres
goose up
go test -tags=integration ./...

実際に使ってみた感想

良かった点:

  • 単体テストが爆速で開発体験が良い
  • 結合テストで本番の問題を早期発見
  • テストの役割が明確で保守しやすい
  • CIのコストが最小限

困った点:

  • go-sqlmockのExpectは書くのが面倒

スピードと正確性、両方のテストを使い分けることで開発体験とテストの品質を両立できた。

まとめ

テストもトレードオフである。スピードと正確性を使い分けることで、両方のメリットを享受できる。

テスト種別 ツール 特徴
単体テスト go-sqlmock 高速・頻繁に実行
結合テスト Docker + Postgres 正確・重要時に実行

ぜひ参考にしてみなさんのプロジェクトに役立ててほしい。

参考

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?