
こんにちは。よっしーです(^^)
本日は、Go言語のよくある質問 について解説しています。
背景
Go言語を学んでいると「なんでこんな仕様になっているんだろう?」「他の言語と違うのはなぜ?」といった疑問が湧いてきませんか。Go言語の公式サイトにあるFAQページには、そんな疑問に対する開発チームからの丁寧な回答がたくさん載っているんです。ただ、英語で書かれているため読むのに少しハードルがあるのも事実で、今回はこのFAQを日本語に翻訳して、Go言語への理解を深めていけたらと思い、これを読んだ時の内容を備忘として残しました。
Packages and Testing
ユニットテストを書くにはどうすればよいですか?
パッケージのソースと同じディレクトリに、_test.go
で終わる新しいファイルを作成してください。そのファイル内で、import "testing"
し、次の形式の関数を書いてください:
func TestFoo(t *testing.T) {
...
}
そのディレクトリでgo test
を実行してください。このスクリプトはTest
関数を見つけ、テストバイナリをビルドし、実行します。
詳細については、How to Write Go Codeドキュメント、testing
パッケージ、およびgo test
サブコマンドを参照してください。
解説
ユニットテストとは?
ユニットテストは、プログラムの各部分(関数やメソッド)が正しく動作するかを自動的にチェックするテストです。バグを早期に発見し、コードの品質を保つために重要です。
基本的な用語
- テストファイル:
_test.go
で終わるファイル - テスト関数:
Test
で始まる関数 - testing.T: テスト結果を報告するためのオブジェクト
- アサーション: 期待する結果と実際の結果を比較すること
ステップバイステップ: 最初のテストを書く
ステップ1: テスト対象のコードを書く
math.go:
package math
// Add は2つの整数を足し算します
func Add(a, b int) int {
return a + b
}
// Multiply は2つの整数を掛け算します
func Multiply(a, b int) int {
return a * b
}
ステップ2: テストファイルを作る
ファイル名のルール: 元のファイル名 + _test.go
mypackage/
├── math.go ← 元のファイル
└── math_test.go ← テストファイル
ステップ3: テストを書く
math_test.go:
package math
import "testing"
// Test で始まる関数名
func TestAdd(t *testing.T) {
// テストケース: 2 + 3 = 5 になるはず
result := Add(2, 3)
expected := 5
if result != expected {
t.Errorf("Add(2, 3) = %d; want %d", result, expected)
}
}
func TestMultiply(t *testing.T) {
// テストケース: 4 * 5 = 20 になるはず
result := Multiply(4, 5)
expected := 20
if result != expected {
t.Errorf("Multiply(4, 5) = %d; want %d", result, expected)
}
}
ステップ4: テストを実行する
# ディレクトリに移動
cd mypackage
# テストを実行
go test
成功した場合の出力:
PASS
ok mypackage 0.002s
失敗した場合の出力:
--- FAIL: TestAdd (0.00s)
math_test.go:10: Add(2, 3) = 6; want 5
FAIL
exit status 1
FAIL mypackage 0.003s
テスト関数の書き方
基本的な構造
func TestXxx(t *testing.T) {
// 1. 準備 (Arrange)
input := 10
// 2. 実行 (Act)
result := MyFunction(input)
// 3. 検証 (Assert)
if result != expected {
t.Errorf("got %v, want %v", result, expected)
}
}
テスト関数の命名規則
✅ 正しい命名:
func TestAdd(t *testing.T) // OK
func TestUserLogin(t *testing.T) // OK
func TestCalculateTax(t *testing.T) // OK
❌ 間違った命名:
func testAdd(t *testing.T) // NG: 小文字で始まっている
func Testadd(t *testing.T) // NG: addが小文字
func AddTest(t *testing.T) // NG: Testで始まっていない
testing.T のメソッド
よく使うメソッド
func TestExample(t *testing.T) {
// エラーを報告するが、テストを続行
t.Error("何か問題が発生しました")
t.Errorf("期待値: %d, 実際: %d", 10, 20)
// エラーを報告し、テストを即座に停止
t.Fatal("致命的なエラー")
t.Fatalf("致命的なエラー: %v", err)
// ログを出力(verbose モードで表示)
t.Log("デバッグ情報")
t.Logf("値: %v", value)
// テストをスキップ
t.Skip("このテストはスキップします")
t.Skipf("条件 %v のためスキップ", condition)
}
Error vs Fatal の違い
func TestComparison(t *testing.T) {
// Error: エラーを報告するが、後続のテストを実行
t.Error("test 1 failed")
t.Error("test 2 failed") // これも実行される
// Fatal: エラーを報告し、即座に終了
t.Fatal("test 3 failed")
t.Error("test 4 failed") // これは実行されない!
}
テーブルドリブンテスト(推奨パターン)
複数のテストケースを効率的に書く方法:
func TestAdd(t *testing.T) {
// テストケースをテーブルで定義
tests := []struct {
name string
a int
b int
expected int
}{
{"正の数", 2, 3, 5},
{"負の数", -1, -1, -2},
{"ゼロ", 0, 5, 5},
{"大きな数", 1000, 2000, 3000},
}
// 各テストケースを実行
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Add(tt.a, tt.b)
if result != tt.expected {
t.Errorf("Add(%d, %d) = %d; want %d",
tt.a, tt.b, result, tt.expected)
}
})
}
}
実行結果:
go test -v
=== RUN TestAdd
=== RUN TestAdd/正の数
=== RUN TestAdd/負の数
=== RUN TestAdd/ゼロ
=== RUN TestAdd/大きな数
--- PASS: TestAdd (0.00s)
--- PASS: TestAdd/正の数 (0.00s)
--- PASS: TestAdd/負の数 (0.00s)
--- PASS: TestAdd/ゼロ (0.00s)
--- PASS: TestAdd/大きな数 (0.00s)
PASS
便利なgoコマンドオプション
# 詳細な出力(-v: verbose)
go test -v
# 特定のテスト関数だけ実行
go test -run TestAdd
# カバレッジを表示
go test -cover
# カバレッジの詳細をHTMLで表示
go test -coverprofile=coverage.out
go tool cover -html=coverage.out
# すべてのサブパッケージをテスト
go test ./...
# 並列実行の数を指定
go test -parallel 4
実践的な例: 構造体のテスト
user.go:
package user
type User struct {
Name string
Email string
Age int
}
func (u *User) IsAdult() bool {
return u.Age >= 18
}
func (u *User) IsValidEmail() bool {
return strings.Contains(u.Email, "@")
}
user_test.go:
package user
import "testing"
func TestUser_IsAdult(t *testing.T) {
tests := []struct {
name string
age int
expected bool
}{
{"未成年", 15, false},
{"ちょうど18歳", 18, true},
{"成人", 25, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
u := &User{Age: tt.age}
result := u.IsAdult()
if result != tt.expected {
t.Errorf("Age %d: got %v, want %v",
tt.age, result, tt.expected)
}
})
}
}
func TestUser_IsValidEmail(t *testing.T) {
tests := []struct {
name string
email string
expected bool
}{
{"有効なメール", "test@example.com", true},
{"無効なメール", "invalid-email", false},
{"空のメール", "", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
u := &User{Email: tt.email}
result := u.IsValidEmail()
if result != tt.expected {
t.Errorf("Email %q: got %v, want %v",
tt.email, result, tt.expected)
}
})
}
}
エラーハンドリングのテスト
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func TestDivide(t *testing.T) {
// 正常ケース
t.Run("正常な割り算", func(t *testing.T) {
result, err := Divide(10, 2)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 5 {
t.Errorf("got %d, want 5", result)
}
})
// エラーケース
t.Run("ゼロ除算", func(t *testing.T) {
_, err := Divide(10, 0)
if err == nil {
t.Fatal("expected error, got nil")
}
})
}
ベストプラクティス
1. テストは独立させる
// ❌ 悪い例: テスト間で状態を共有
var counter int
func TestA(t *testing.T) {
counter++ // 他のテストに影響
}
// ✅ 良い例: 各テストで独立した状態
func TestB(t *testing.T) {
counter := 0 // ローカル変数
counter++
}
2. 分かりやすいエラーメッセージ
// ❌ 悪い例
if result != expected {
t.Error("failed")
}
// ✅ 良い例
if result != expected {
t.Errorf("Add(%d, %d) = %d; want %d", a, b, result, expected)
}
3. テーブルドリブンテストを使う
// ✅ 推奨: 複数のケースをまとめて管理
tests := []struct{
name string
input int
want int
}{
{"ケース1", 1, 2},
{"ケース2", 2, 4},
}
まとめ
Goでユニットテストを書くのはとてもシンプル:
- ✅
_test.go
で終わるファイルを作る - ✅
import "testing"
する - ✅
func TestXxx(t *testing.T)
の形式で書く - ✅
go test
で実行 - ✅ テーブルドリブンテストで複数ケースを効率的に
Goのテストの特徴
- シンプル: 外部ライブラリ不要、標準機能だけでOK
- 高速: 並列実行が簡単
- 統一: すべてのGoプロジェクトで同じ方法
- 組み込み:
go test
コマンド1つで実行
テストを書くことで:
- 🐛 バグを早期発見
- 📝 コードの仕様を文書化
- 🔧 リファクタリングが安全に
- 💪 コードの品質が向上
最初は面倒に感じるかもしれませんが、テストを書く習慣をつけることで、長期的には開発がずっと楽になります!
おわりに
本日は、Go言語のよくある質問について解説しました。

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)
コメント