Go テスト 2026年版:ユニットテスト、モック、技術面接の質問

テーブル駆動テスト、モック、面接官が期待するパターンでGoのテストをマスターします。testifyとgomockを使った実践的な例を紹介します。

Go Testing 2026

Goのテストは標準のtestingパッケージに依存しており、すべてのGoインストールに同梱され、外部依存関係を必要としません。他の言語のフレームワークとは異なり、Goのアプローチはシンプルさを重視しています。テストファイルはプロダクションコードと同じディレクトリに配置され、テスト関数は命名規則に従い、go testコマンドが検出と実行を処理します。

面接官が注目するポイント

テーブル駆動テストを書き、依存性注入にインターフェースを使用し、モックが価値を追加する場面とノイズを追加する場面を理解している候補者が評価されます。

testingパッケージを使ったユニットテストの書き方

すべてのテストファイルは_test.goで終わり、テスト対象のコードと同じパッケージに配置されます。テスト関数はTestで始まり、その後に大文字で始まる名前が続きます。*testing.Tパラメータは失敗を報告するためのメソッドを提供します。

calculator_test.gogo
package calculator

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d; want 5", result)
    }
}

go test ./...を実行すると、現在のモジュール内のすべてのテストが実行されます。-vフラグは個々のテスト名とそのパス/失敗ステータスを表示します。

テーブル駆動テスト:Go標準のパターン

テーブル駆動テストは重複を減らし、新しいケースの追加を容易にします。テーブルの各行は、入力と期待される出力を持つ1つのシナリオを表します。このパターンはGo標準ライブラリや業界全体のプロダクションコードベースで見られます。

validator_test.gogo
package validator

import "testing"

func TestValidateEmail(t *testing.T) {
    tests := []struct {
        name    string
        email   string
        wantErr bool
    }{
        {"valid email", "user@example.com", false},
        {"missing at sign", "userexample.com", true},
        {"empty string", "", true},
        {"unicode local part", "user@例え.jp", false},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := ValidateEmail(tt.email)
            if (err != nil) != tt.wantErr {
                t.Errorf("ValidateEmail(%q) error = %v, wantErr %v",
                    tt.email, err, tt.wantErr)
            }
        })
    }
}

t.Runメソッドは独立して実行されるサブテストを作成します。失敗したサブテストは、他のケースを停止することなく、どの特定のケースが失敗したかを報告します。

インターフェースによる依存性注入

Goのインターフェースは、外部システム(データベース、HTTPクライアント、ファイルシステム)に依存するコードのテストを可能にします。使用箇所で最小限のインターフェースを定義することで、実際の実装をテストダブルに置き換えることができます。

service.gogo
package order

// Repository defines the data access methods this service needs
type Repository interface {
    FindByID(id string) (*Order, error)
    Save(order *Order) error
}

// Service handles order business logic
type Service struct {
    repo Repository
}

// NewService creates a service with the given repository
func NewService(repo Repository) *Service {
    return &Service{repo: repo}
}

// Process validates and saves an order
func (s *Service) Process(order *Order) error {
    if order.Total <= 0 {
        return ErrInvalidTotal
    }
    return s.repo.Save(order)
}

ServiceRepositoryを満たす任意の型を受け入れます。プロダクションコードはデータベースベースの実装を渡し、テストはモックまたはスタブを渡します。

gomockとmockgenを使ったモック

gomockパッケージはインターフェース定義からモック実装を生成します。mockgenコマンドはソースファイルを読み込み、呼び出しを記録し設定された値を返すモック構造体を生成します。

bash
# Generate mocks for the Repository interface
mockgen -source=service.go -destination=mocks/repository_mock.go -package=mocks

生成されたモックをテストで使用する例:

service_test.gogo
package order

import (
    "testing"

    "github.com/stretchr/testify/assert"
    "go.uber.org/mock/gomock"

    "yourmodule/order/mocks"
)

func TestService_Process_SavesValidOrder(t *testing.T) {
    ctrl := gomock.NewController(t)
    mockRepo := mocks.NewMockRepository(ctrl)

    order := &Order{ID: "123", Total: 99.99}

    // Expect Save to be called once with this order, return nil
    mockRepo.EXPECT().
        Save(order).
        Return(nil).
        Times(1)

    svc := NewService(mockRepo)
    err := svc.Process(order)

    assert.NoError(t, err)
}

func TestService_Process_RejectsZeroTotal(t *testing.T) {
    ctrl := gomock.NewController(t)
    mockRepo := mocks.NewMockRepository(ctrl)

    // No calls expected to repo since validation fails first

    svc := NewService(mockRepo)
    err := svc.Process(&Order{ID: "456", Total: 0})

    assert.ErrorIs(t, err, ErrInvalidTotal)
}

モックは、Saveが期待される引数で正確に1回呼び出されたことを検証します。テスト対象のコードがSaveを呼び出さない場合、または異なる引数で呼び出した場合、テストは失敗します。

testifyを使ったアサーションとスイート

testifyは、手動のif文チェックよりも明確な失敗メッセージを生成するアサーション関数を提供します。assertパッケージは失敗後も実行を継続します。requireパッケージはテストを即座に停止します。

user_test.gogo
package user

import (
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

func TestParseUser(t *testing.T) {
    input := `{"id": "u1", "name": "Alice"}`

    user, err := ParseUser(input)

    require.NoError(t, err, "parsing should not fail")
    assert.Equal(t, "u1", user.ID)
    assert.Equal(t, "Alice", user.Name)
    assert.Empty(t, user.Email, "email should default to empty")
}

require.NoError呼び出しは、パースが失敗した場合にテストを停止し、後続のアサーションでのnilポインタパニックを防ぎます。

Goの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

httptestを使ったHTTPハンドラのテスト

net/http/httptestパッケージは、ハンドラ出力をキャプチャするためのResponseRecorderと、ローカルテストサーバーを実行するためのServerを提供します。ほとんどのユニットテストは、ネットワークオーバーヘッドを回避するためにResponseRecorderを使用します。

handler_test.gogo
package api

import (
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/stretchr/testify/assert"
)

func TestHealthHandler(t *testing.T) {
    req := httptest.NewRequest(http.MethodGet, "/health", nil)
    rec := httptest.NewRecorder()

    HealthHandler(rec, req)

    assert.Equal(t, http.StatusOK, rec.Code)
    assert.JSONEq(t, `{"status": "ok"}`, rec.Body.String())
}

外部サービスを呼び出すハンドラの場合は、モッククライアントを注入するか、httptest.NewServerを使用してフェイクバックエンドを作成します。

Goテストに関するよくある面接質問

面接官は、実際のコードベースでの経験を明らかにする質問を通じてテストの知識を評価します。準備の整った候補者を区別するパターンは以下の通りです:

「外部APIを呼び出す関数をどのようにテストしますか?」

HTTPクライアント用のインターフェースを定義し、関数または構造体に注入し、事前に決められたレスポンスを返すモックを提供します。これによりネットワーク呼び出しを回避し、テストを決定論的にします。

「スタブとモックをいつ使い分けますか?」

スタブは、呼び出され方を検証せずに定型のレスポンスを返します。モックは、特定のメソッドが特定の引数で呼び出されたことを検証します。戻り値がインタラクションよりも重要な場合はスタブを使用します。インタラクションの検証がテストの目的である場合はモックを使用します。

「テーブル駆動テストとは何ですか?なぜGoはそれを好むのですか?」

テーブル駆動テストは、テストケースを構造体のスライスのデータとして定義します。ボイラープレートを削減し、ケースの追加を容易にし、t.Runで読みやすいテスト出力を生成します。このパターンは、巧妙な抽象化よりも明示的で反復的なコードを好むGoの哲学に合致しています。

「複数のテストが共有するテストフィクスチャやセットアップをどのように処理しますか?」

パッケージレベルのセットアップとティアダウンにはTestMainを使用します。関連するテスト間で共有されるセットアップには、ヘルパー関数またはテストスイート(testifyから)を使用します。テスト間の結合を生み出すグローバル状態を避けます。

Goの面接準備をしていますか?Goテストモジュールでは、これらのパターンを練習問題とともに詳しく説明しています。

カバレッジとレース検出を使ったテスト実行

go testコマンドは、未テストのコードパスと並行性のバグを明らかにするフラグを受け入れます。

bash
# Run tests with coverage report
go test -cover ./...

# Generate HTML coverage visualization
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html

# Detect race conditions (slower, but catches real bugs)
go test -race ./...

レース検出器はメモリアクセスを計装し、同じ変数への並行読み書きをフラグ付けします。CIで-raceフラグを付けてテストを実行すると、特定のタイミングでのみ発生するバグをキャッチできます。

大規模コードベースでのテスト整理

プロジェクトが成長するにつれて、テストの整理は保守性に影響を与えます:

  • 同一パッケージテストpackage foo)は非公開関数やフィールドにアクセスできます。内部動作を検証するユニットテストに使用します。
  • 外部パッケージテストpackage foo_test)は外部コードのようにパッケージをインポートします。統合テストや公開APIの検証に使用します。
  • testdataディレクトリはJSONファイル、ゴールデン出力、シードデータベースなどのフィクスチャを保存します。Goはビルド時にこれらを無視します。
  • ビルドタグは外部システムを必要とする統合テストからユニットテストを分離します。go test -tags=integration ./...を実行してそれらを含めます。

テスト以外のGoベストプラクティスについては、並行性パターンの記事でゴルーチンとチャネルについて説明しています。

Goテストで覚えておくべきこと

  • testingパッケージはGoに同梱されており、外部依存関係を必要としません
  • テーブル駆動テストは重複を減らし、面接で期待されるパターンです
  • インターフェースは依存性注入を可能にします。実装箇所ではなく使用箇所で定義します
  • gomockはインターフェースからモックを生成します。testifyはより明確なアサーションを提供します
  • httptest.ResponseRecorderはネットワークオーバーヘッドなしでハンドラ出力をキャプチャします
  • -raceフラグはユニットテストでは見逃される並行性のバグを検出します
  • カバレッジメトリクスは注意を導きますが、正確性を保証するものではありません

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

今日のチャレンジ

Go のバグを見つけられますか

実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

Anthony Fillion-Maillet

執筆

Anthony Fillion-Maillet

SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年8月24日 更新

タグ

#go
#testing
#golang
#unit-tests
#mock
#interview

共有

関連記事