テーブルドリブンテスト

最終更新:2020/05/14


目次

  1. テーブルドリブンテストとは
  2. テーブルドリブンテストの書き方
  3. テーブルドリブンテストの欠点

1. テーブルドリブンテストとは

Go言語のテーブルドリブンテストは、テスト手法のデータドリブンテストのことです。

データドリブンテストは、コードからテストの入力と出力値が区切られているテストの方法です。

2. テーブルドリブンテストの書き方

テーブルドリブンテストは以下のようなコードになります。

package usecase_test import ( "errors" "net/http" "testing" "github.com/golang/mock/gomock" ) const ( CaseEmptyUserID = 0 CaseInvalidUserID = 1 CaseNotFoundUser = 2 CaseUserDeleteFailure = 3 CaseUserDeleteSuccess = 4 ) func TestUserDeleteUseCaseDo(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() cases := []struct { UserID string IntUserID int TestPattern int }{ {UserID: "", IntUserID: 0, TestPattern: CaseEmptyUserID}, {UserID: "aaaaa", IntUserID: 0, TestPattern: CaseInvalidUserID}, {UserID: "100", IntUserID: 100, TestPattern: CaseNotFoundUser}, {UserID: "100", IntUserID: 100, TestPattern: CaseUserDeleteFailure}, {UserID: "100", IntUserID: 100, TestPattern: CaseUserDeleteSuccess}, } for _, c := range cases { mockUsersRepogitory := NewMockUsersRepogitory(ctrl) if c.TestPattern == CaseNotFoundUser { mockUsersRepogitory.EXPECT().FindByID(c.IntUserID).Return(nil, errors.New(config.GormRecordNotFound)) } else if c.TestPattern == CaseUserDeleteFailure { user, _ := fixture.FindByUserID(c.IntUserID) mockUsersRepogitory.EXPECT().FindByID(c.IntUserID).Return(user, nil) mockUsersRepogitory.EXPECT().DeleteByUserID(c.IntUserID).Return(errors.New("somehing")) } else if c.TestPattern == CaseUserDeleteSuccess { user, _ := fixture.FindByUserID(c.IntUserID) mockUsersRepogitory.EXPECT().FindByID(c.IntUserID).Return(user, nil) mockUsersRepogitory.EXPECT().DeleteByUserID(c.IntUserID).Return(nil) } u := usecase.NewUserDeleteUseCase(mockUsersRepogitory) code, obj := u.Do(c.IntUserID, nil) switch c.TestPattern { case CaseEmptyUserID: assertFailure(code, http.StatusBadRequest, config.RequiredUserID, obj, t) case CaseInvalidUserID: assertFailure(code, http.StatusBadRequest, config.InvalidUserID, obj, t) case CaseNotFoundUser: assertFailure(code, http.StatusBadRequest, config.UserNotFound, obj, t) case CaseUserDeleteFailure: assertFailure(code, http.StatusInternalServerError, config.UnexpectedErrorCode, obj, t) case CaseUserDeleteSuccess: assertSuccess(code, http.StatusNoContent, t) default: } } } func assertFailure(code int, expectCode int, expectErrorCode string, obj interface{}, t *testing.T) { if code != expectCode { t.Errorf("got %v, want %v", code, expectCode) } result := obj.(api_response.Response) if result.Errors[0].Code != expectErrorCode { t.Errorf("got %v, want %v", result.Errors[0].Code, expectErrorCode) } } func assertSuccess(code int, expectCode int, t *testing.T) { if code != expectCode { t.Errorf("got %v, want %v", code, expectCode) } }

このコードは、ユーザー情報を削除するAPIのテーブルドリブンテストです。

テストパターンとして

  • ユーザIDが空白
  • ユーザIDが無効
  • ユーザ情報なし
  • ユーザ削除失敗
  • ユーザ削除成功

を定義しています。

テーブルドリブンテストのポイントは

テストケースを宣言する

ことです。

cases := []struct { UserID string IntUserID int TestPattern int }{ {UserID: "", IntUserID: 0, TestPattern: CaseEmptyUserID}, {UserID: "aaaaa", IntUserID: 0, TestPattern: CaseInvalidUserID}, {UserID: "100", IntUserID: 100, TestPattern: CaseNotFoundUser}, {UserID: "100", IntUserID: 100, TestPattern: CaseUserDeleteFailure}, {UserID: "100", IntUserID: 100, TestPattern: CaseUserDeleteSuccess}, }

このことにより、一つのテスト関数で複数のテストを実行することができます。

なので、重複せざるを得ないテストコードを減らすことができます。

3. テーブルドリブンテストの欠点

テーブルドリブンテストの欠点は、

コードの複雑度が高くなる

ことです。

テストをコードにひとまとめにするのだから当たり前とも言えます。

なので、

gocyclo

のような複雑度を測るツールを導入しているとテストでひっかかります。

これを防ぐには

  • テストコードを分割する
  • テストコードは複雑度を計測しない
  • データドリブンテストは利用しない

という手段があります。

一番のオススメは

テストコードを分割する

です。

面倒ですが、可読性が良くなるのでテストも変更に強くなります。

欠点は、テストコードを書くにも工数がかかることです。