Go のテスト基礎 - testing パッケージと go test

Go には標準ライブラリに testing パッケージが組み込まれており、外部ツールなしでテストを書いて実行できる。他の言語では JUnit や pytest のようなフレームワークを別途導入する必要があるが、Go ではそうした手間がない。

テストファイルの命名規則

テストコードは _test.go というサフィックスを持つファイルに書く。たとえば math.go に対するテストは math_test.go になる。この命名規則により、go build 時にはテストファイルが自動的に除外され、go test 時にだけコンパイル対象になる。

// math.go
package calc

func Add(a, b int) int {
    return a + b
}

対応するテストファイルは次のようになる。

// math_test.go
package calc

import "testing"

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

テスト関数は必ず Test で始まり、引数に *testing.T を受け取る。この規則を守らないと go test がテスト関数として認識しない。

go test コマンド

テストの実行は go test コマンドで行う。カレントパッケージのテストを実行する場合はそのまま叩けばいい。

# カレントパッケージのテストを実行
go test

# 詳細な出力を表示
go test -v

# プロジェクト全体のテストを実行
go test ./...

-v フラグを付けると各テスト関数の名前と結果が個別に表示される。デフォルトでは失敗したテストだけが出力されるため、すべてパスした場合は ok とだけ表示される。

go test(デフォルト)

失敗時のみ詳細を出力。成功なら ok の一行だけ

go test -v

すべてのテスト関数の名前・結果・ログを個別に出力

t.Errorf と t.Fatalf の使い分け

テストが期待と異なる結果を返したとき、報告の方法は大きく2つある。t.Errorf はエラーを記録するが、テスト関数の残りの処理は続行する。一方 t.Fatalf はエラーを記録した直後にそのテスト関数を中断する。

func TestDivide(t *testing.T) {
    result, err := Divide(10, 0)
    if err == nil {
        t.Fatalf("expected error for division by zero, got nil")
    }
    // t.Fatalf で中断されるので、ここには到達しない

    result, err = Divide(10, 2)
    if err != nil {
        t.Errorf("unexpected error: %v", err)
    }
    if result != 5 {
        t.Errorf("Divide(10, 2) = %f, want 5", result)
    }
}

後続の処理が前提条件に依存する場合は t.Fatalf を使い、独立した検証であれば t.Errorf で続行させるのが一般的だ。

t.Errorf を使う場面

複数の独立したアサーションを1つの関数内で行いたいとき。すべての失敗を一度に確認できる。

t.Fatalf を使う場面

前提条件が崩れたら後続の検証が無意味になるとき。nil チェックや初期化エラーの検出など。

テストの実行パターン

特定のテスト関数だけを実行したい場合は -run フラグを使う。正規表現でマッチするテスト関数を絞り込める。

# TestAdd だけを実行
go test -v -run TestAdd

# "Divide" を含むテスト関数を実行
go test -v -run Divide

また、テストにはキャッシュが効く仕組みになっている。ソースコードに変更がなければ前回の結果がそのまま使われ、(cached) と表示される。キャッシュを無視して再実行したい場合は -count=1 を付ける。

# キャッシュを無視して実行
go test -count=1 ./...

テスト失敗時のメッセージ設計

テストが失敗したときのメッセージは、何が起きたのかを素早く理解できるように書く。Go のコミュニティでは gotwant を使った形式が広く使われている。

if got != want {
    t.Errorf("Add(%d, %d) = %d, want %d", a, b, got, want)
}

関数名、入力値、実際の出力、期待値をすべて含めることで、失敗時にログを見ただけで原因を特定できる。曖昧なメッセージは避けるべきだ。

悪いメッセージ

t.Errorf("test failed") - 何が失敗したのか分からない

良いメッセージ

t.Errorf("Add(2, 3) = %d, want %d", got, want) - 入力・出力・期待値が明確

testing パッケージは小さな API でありながら、Go のテスト文化を支える基盤になっている。外部ライブラリに頼らず、標準の仕組みだけで十分に実用的なテストが書ける点が Go らしい設計思想と言えるだろう。