はじめに
Go言語ではテストに関する機能が備わったtestingというパッケージが標準で用意されております。
今回はGo言語でテストを書く方法のうち、「テーブル駆動テスト」と「サブテスト方式」という2つの方法について見ていきます。この2つの方法を上手に使い分けることで、わかりやすくて維持しやすいテストを書くことができるのですが、使い分けについて改めて学んでみることにしました。
テーブル駆動テスト
テーブル駆動テストは、たくさんのテストパターンを1つの表(テーブル)にまとめて表現し、その表を1つずつ見ながらテストを実行する方法です。テーブル駆動テストを使うと、同じようなテストを何度も書かなくて済むので、テストコードがスッキリします。また、新しいテストパターンを追加したり、既存のテストパターンを変更したりするのも簡単になります。Go言語といえばこのテスト方式がテッパンでしょう。
以下は、テーブル駆動テストの基本的な形を示したサンプルコードです。
func TestSum(t *testing.T) { var tests = []struct { a, b int want int }{ {1, 2, 3}, {2, 3, 5}, {5, 5, 10}, } for _, tt := range tests { if got := Sum(tt.a, tt.b); got != tt.want { t.Errorf("Sum(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want) } } }
このサンプルコードでは、tests
という表(スライス)にテストパターンを定義しています。各テストパターンは、入力する値a
とb
、期待する結果want
を持つ構造体で表現されています。
for
ループを使ってテストパターンを1つずつ取り出し、Sum
関数を呼び出します。関数の結果と期待する結果を比べて、違う場合はエラーを報告します。
テーブル駆動テストは、以下のようなときに特に効果的です。
- 同じ関数に対して、いろいろな入力値と期待する結果のパターンがあるとき。
- 関数の動きが入力値によって大きく変わらないとき。
- テストパターンの数が多いとき。
テーブル駆動テストを使うことで、テストコードが読みやすくなり、管理しやすくなります。また、テストパターンを追加したり変更したりするのが簡単になるので、テストを長く使い続けることができます。
サブテスト方式
サブテスト方式は、1つのテスト関数の中で、さらに細かいテストを実行する方法です。t.Run
という機能を使って、テスト関数の中にサブテストを作ります。サブテストを使うと、テストの目的や条件ごとにテストを分けて書くことができるので、テストコードが読みやすくなります。
以下は、サブテスト方式を使ったサンプルコードです。
func TestSplit(t *testing.T) { t.Run("Normal", func(t *testing.T) { got := Split("a,b,c", ",") want := []string{"a", "b", "c"} if !reflect.DeepEqual(got, want) { t.Errorf("Split(\"a,b,c\", \",\") = %v, want %v", got, want) } }) t.Run("Empty", func(t *testing.T) { got := Split("", ",") want := []string{} if !reflect.DeepEqual(got, want) { t.Errorf("Split(\"\", \",\") = %v, want %v", got, want) } }) t.Run("No delimiter", func(t *testing.T) { got := Split("abc", "") want := []string{"a", "b", "c"} if !reflect.DeepEqual(got, want) { t.Errorf("Split(\"abc\", \"\") = %v, want %v", got, want) } }) }
このサンプルコードでは、TestSplit
という1つのテスト関数の中に、3つのサブテスト("Normal"
、"Empty"
、"No delimiter"
)を作っています。それぞれのサブテストでは、Split
関数に対して異なる入力値を与え、期待する結果と比較しています。
サブテスト方式は、以下のようなときに特に効果的です。
- テストしたい関数の使い方が複雑で、いくつかの異なる条件下でテストする必要があるとき。
- テストパターンごとに、テストの準備(セットアップ)や後片付け(クリーンアップ)が異なるとき。
- テストコードを読みやすくしたいとき。
サブテスト方式を使うことで、テストの目的や条件が明確になり、テストコードの理解が深まります。また、特定の条件だけをテストしたいときにも、サブテストを個別に実行できるので便利です。
テーブル駆動テストとサブテスト方式の使い分け
テーブル駆動テストとサブテスト方式は、それぞれ適した使い方があります。以下のようなポイントを考慮して、2つの方式を使い分けましょう。
テストパターンの似ている度合い
- テストパターンの内容が似ていて、同じような手順でテストできる場合は、テーブル駆動テストが適しています。
- テストパターンごとに、異なる手順や条件が必要な場合は、サブテスト方式の方が良いでしょう。
テストの準備と後片付けの共通性
- テストパターンごとに、テストの準備や後片付けが異なる場合は、サブテスト方式を使うことで、それぞれのテストパターンに合わせた準備と後片付けができます。
- テストの準備と後片付けが共通の場合は、テーブル駆動テストを使うとシンプルに書けます。
テストコードの読みやすさ
- テーブル駆動テストは、入力値と期待する結果を表形式で表現するので、テストパターンが一目で分かりやすくなります。
- サブテスト方式は、それぞれのテストパターンに名前を付けられるので、テストの目的や内容が明確になります。
テストパターンの数
- テストパターンの数が多い場合は、テーブル駆動テストを使うことで、コードの繰り返しを減らし、テストを管理しやすくなります。
- テストパターンの数が少ない場合は、サブテスト方式でも十分に管理できます。
表にしてみました↓
観点 | テーブル駆動テスト | サブテスト方式 |
---|---|---|
テストパターンの類似性 | 高い | 低い |
テストの準備と後片付け | 共通 | 個別 |
テストコードの読みやすさ | テストパターンが一目で分かる | テストの目的や内容が明確 |
適したテストパターンの数 | 多い | 少ない |
まとめ
使い分け方が大体整理できました。これを活かすのであれば、ハンドラーなどの中心的な処理はテストしたいパターンが多くなることがあるのでサブテストにして、そのハンドラー内で呼び出しているDBアクセスのメソッドはテーブル駆動テストにするのが良さそうです。