Go言語のテストコード設計パターン集!保守性の高い書き方を学ぼう
生徒
「Go言語でテストを書くとき、どんな書き方が良いんですか?」
先生
「テストの書き方にもパターンがあります。保守性を高めるためには、読みやすく再利用しやすい設計が重要です。」
生徒
「保守性ってどういう意味ですか?」
先生
「保守性とは、後からコードを修正したり、追加したりするときに簡単で安全に行える性質のことです。テストコードも同じで、変更に強い書き方が望まれます。」
生徒
「具体的なパターンにはどんなものがありますか?」
先生
「では、いくつか代表的なパターンを見ていきましょう。」
1. テーブル駆動テスト(Table-Driven Test)
テーブル駆動テストは、入力と期待結果を表形式でまとめてテストする方法です。これにより、同じ関数に対して複数のケースを簡潔に書くことができます。例えば計算関数のテストで有効です。
func TestAdd(t *testing.T) {
cases := []struct{
a, b int
expected int
}{
{1, 2, 3},
{2, 3, 5},
{5, 5, 10},
}
for _, c := range cases {
t.Run(fmt.Sprintf("%d+%d", c.a, c.b), func(t *testing.T) {
result := Add(c.a, c.b)
if result != c.expected {
t.Errorf("期待値 %d ですが、結果は %d です", c.expected, result)
}
})
}
}
このパターンは、ケースが増えてもコードを簡潔に保てるため保守性が高くなります。
2. モックを使った依存関係の分離
関数や構造体が外部サービスやファイルなどに依存する場合、テストではその依存を分離することが重要です。Goでは、モック(疑似的な実装)を使うことで、外部要素に左右されない安定したテストが可能です。
type Database interface {
GetUser(id int) User
}
type MockDB struct{}
func (m MockDB) GetUser(id int) User {
return User{ID: id, Name: "テストユーザー"}
}
このようにモックを使うと、本番のデータベースに触れずにテストが行えます。
3. SetupとTeardownの活用
Setupはテスト前の初期化処理、Teardownはテスト後の後片付けです。共通処理をまとめることで、テストコードの重複を減らし、変更に強い設計になります。
func setup() *Service {
svc := NewService()
// 初期化処理
return svc
}
func TestService(t *testing.T) {
svc := setup()
defer svc.Close()
// テスト本体
}
4. サブテストの活用
Goのt.Runを使うと、1つのテスト関数内で複数のサブテストを作れます。これにより、どのケースで失敗したかが明確になり、デバッグが簡単になります。
5. 名前付きテストとドキュメンテーション
テスト関数やサブテストには意味のある名前を付けることが重要です。例えばTestAdd_PositiveNumbersのようにすると、何をテストしているかが一目でわかります。コメントを活用して、テストの意図を簡単に説明しておくと保守性がさらに高まります。
6. まとめて実行しやすい設計
テストは、個別でも実行できることが望ましいですが、まとめて実行することも想定して設計すると便利です。テーブル駆動テストやサブテストを組み合わせると、テストの網羅性を保ちながら効率的に実行できます。
7. 初心者におすすめの練習方法
まずは小さな関数でテーブル駆動テストやサブテストを試してみましょう。次に依存関係をモックで置き換える練習を行い、最後にSetupとTeardownで共通処理を整理するステップで学ぶと理解が深まります。これを繰り返すことで、保守性が高く、読みやすいテストコードを書く力が身につきます。