こんにちは。KOUKIです。
Go言語で、Mockのテスト方法を学んだので、共有します。
テストデータの作成をMockを使って、省略します!
<目次>
プロジェクトの作成
プロジェクトを作成します。
1 2 3 4 5 6 |
mkdir go-mock cd go-mock touch storage.go touch storage_test.go touch cook.go mkdir mock |
Mockインストール
次のパッケージをインストールしてください。
1 2 3 4 |
go get github.com/golang/mock/gomock go get github.com/golang/mock/mockgen go get github.com/stretchr/testify/assert |
テスト対象のファイルを作成
Mockのテストでは、テスト対象となるインターフェースを定義する必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// storage.go package restaurant //go:generate mockgen -destination=./mock/storage.go -package=mock github.com/hoge/go-mock Storage type Storage interface { GetIngredients() map[string]int } type storage struct { ingredients map[string]int } func NewStorage(ingredients map[string]int) Storage { return &storage{ingredients: ingredients} } func (s *storage) GetIngredients() map[string]int { ingredientsCPY := make(map[string]int, len(s.ingredients)) for name, quantity := range s.ingredients { ingredientsCPY[name] = quantity } return ingredientsCPY } |
上記の「//go:generate mockgen -destination=./mock/storage.go -package=mock github.com/hoge/go-mock Storage」は大切です。
Mockファイルを作成する時の条件になります。
上記に設定したStorageインターフェースに対して、テストコードを書く予定です。
ちなみに、「GetIngredients」は自身が保有するMapデータ(ingredients)のコピーを返します。
本来であれば、ingredientsに格納するテストデータを作成する必要があるのですが、Mockを使ってそれを省略します(それが本記事の目的です)
また、Storageを利用するCook処理についても実装しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// cook.go package restaurant type Cook struct { storage Storage } func NewCook(storage Storage) Cook { return Cook{storage: storage} } func (c *Cook) CreateMedamaYaki() map[string]int { // 材料を取得 ingredients := c.storage.GetIngredients() var medamayakiDishes = make(map[string]int) // 卵を確認 eggNum, ok := ingredients["egg"] if eggNum == 0 || !ok { return nil } medamayakiDishes["medamayaki"] = eggNum return medamayakiDishes } |
上記のコードでは、CreateMedamaYakiで、目玉焼きを作るメソッドを実装しています。
ここでは、StorageのGetIngredientsを呼び出しています。
Mockファイルの作成
以下のコマンドを実行して、Mockファイルを作成しましょう。
1 |
go generate -run="mockgen" ./... |
コマンドの実行に成功するとmockフォルダ配下にMockファイル(storage.go)が作成されているはずです。
1 2 3 4 5 6 |
go-mock ├── cook.go ├── cook_test.go ├── mock │ └── storage.go └── storage.go |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
// storage.go / Code generated by MockGen. DO NOT EDIT. // Package mock is a generated GoMock package. package mock import ( gomock "github.com/golang/mock/gomock" reflect "reflect" ) // MockStorage is a mock of Storage interface type MockStorage struct { ctrl *gomock.Controller recorder *MockStorageMockRecorder } // MockStorageMockRecorder is the mock recorder for MockStorage type MockStorageMockRecorder struct { mock *MockStorage } // NewMockStorage creates a new mock instance func NewMockStorage(ctrl *gomock.Controller) *MockStorage { mock := &MockStorage{ctrl: ctrl} mock.recorder = &MockStorageMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use func (m *MockStorage) EXPECT() *MockStorageMockRecorder { return m.recorder } // GetIngredients mocks base method func (m *MockStorage) GetIngredients() map[string]int { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetIngredients") ret0, _ := ret[0].(map[string]int) return ret0 } // GetIngredients indicates an expected call of GetIngredients func (mr *MockStorageMockRecorder) GetIngredients() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIngredients", reflect.TypeOf((*MockStorage)(nil).GetIngredients)) } |
テストコードの実装
cook_test_goに以下の実装をしてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package restaurant import ( "testing" "github.com/golang/mock/gomock" "github.com/hoge/go-mock/mock" "github.com/stretchr/testify/assert" ) func TestStorage_GetIngredients(t *testing.T) { // Mockのコントローラーを作成 ctrl := gomock.NewController(t) // storageをMock化 storageMock := mock.NewMockStorage(ctrl) // storageMockをNewCookに渡す // NewCookは、Cookのコンストラクタである // storageMockはGetIngredientsを実装しているので引数として渡せる c := NewCook(storageMock) // Returnに返したデータを設定 // Storageは空とする => データ作成 storageMock.EXPECT().GetIngredients().Return(map[string]int{}) medamayakiDishes := c.CreateMedamaYaki() assert.True(t, medamayakiDishes == nil) // Returnに返したデータを設定 // Storageは{"egg": 5}とする => データ作成 storageMock.EXPECT().GetIngredients().Return(map[string]int{"egg": 5}) medamayakiDishes = c.CreateMedamaYaki() assert.True(t, medamayakiDishes["medamayaki"] == 5) } |
大体の説明は、コメントに書きました。
テストを実行しましょう。
1 2 3 |
$ go test -run="TestStorage_GetIngredients" PASS ok github.com/hoge/go-mock 0.033s |
のように戻り値として設定したい値をMock化できるので、超便利ですね。storageMock.EXPECT().GetIngredients().Return(map[string]int{"egg": 5})
インターフェースとして使えないというのは、
の部分があるからです。c := NewCook(storageMock)
NewCookはもともとStorage(インターフェース)を引数として持ちますが、GetIngredientsを実装しているオブジェクトならなんでも渡せます。
引数として渡しているstorageMockは、下記のようにGetIngredientsをメソッドとして実装しているので、NewCookが実行できるわけです。(Storageインターフェースに定義しているGetIngredientsを持っているため)
1 2 3 4 5 6 7 8 9 |
// mock/storage.go // GetIngredients mocks base method func (m *MockStorage) GetIngredients() map[string]int { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetIngredients") ret0, _ := ret[0].(map[string]int) return ret0 } |
TypeScriptでも勉強しましたが、インターフェースはなかなか便利ですね。
おわりに
ちょっと説明が乱暴でしたが、Mockを使ってデータを簡単に用意できることがお分かり頂けたと思います。
本記事で紹介したMockの機能は、ほんの一部に過ぎません。
色々と試されるといいと思います。
それでは、また!
最近のコメント