こんにちは。まいたけと申します。
こちらは、Go4 Advent Calendar 2019 の23日目の記事です。
Goで実装して、テストも実装して、、とやっていると、
ある程度以上の大きさ(&複雑さ?)の実装となるとmockが必要になるケースが増えるかなと思います。
GoMockを使って実装するやり方は結構情報あったのですが、
標準のtestingパッケージのみでmockを作る方法を詳しく書いている方があまり多くないようで(皆さんGoMockを利用されているということでしょうかね。。)
Goに入門して割りとすぐの頃にmockを作ろうとして少し苦労した記憶があるので、まとめてみたいと思います。
テスト対象の実装
createTask
とupdateTask
のmockを作りたいとする。
type TaskInterface interface {
createTask(int,string) (string, error)
updateTask(int,string) (string, error)
}
type TaskController struct {
functions TaskInterface
}
func (c *TaskController) createTask(id int,title string) error {
// 何か処理
}
func (c *TaskController) updateTask(id int,title string) error {
// 何か処理
}
test側
Goのinterfaceはダックタイピングを採用しているため、TaskInterface
のメソッドリストを実装してあげることで、mockを作成出来ます。
type mockTaskController struct {
TaskInterface
mockCreateTask func(int,string) (string, error)
mockUpdateTask func(int,string) (string, error)
}
// 内部処理はmockCreateTask,mockUpdateTaskに任せる
func (m *mockTaskController) createTask(id int, title string) error {
m.mockCreateTask(id, title)
}
func (m *mockTaskController) updateTask(id int, title string) error {
m.mockUpdateTask(id, title)
}
... //中略
func TestHoge(t *testing.T) {
m = &mockTaskController{}
// テスト内容に合わせてmockCreateTaskの戻り値を指定する
m.mockCreateTask = func(int,string) (string, error) { return "bug fix", nil }
...
}
メソッドの内部処理は上記の例のように固定値を返すようにしても良いですが、
createTask
の戻り値によって動作が変わるメソッドのテストを書く場合もある(むしろこっちのほうが多い?)ので、テーブルドリブンテストのループ内でm.mockCreateTask
の宣言をする感じにも出来ます。
test側 テーブルドリブンテストの場合
func TestHoge(t *testing.T) {
m = &mockTaskController{}
Cases := []struct {
caseTitle string
createTaskTitle string
createTaskError
}
.... // 中略
}
for _, c := range Cases {
m.mockCreateTask = func(int,string) (string, error) { return c.createTaskTitle, c.createTaskError }
... //中略
}
...
}
まとめ
この方法だと既存のソース、パッケージ内のメソッドのmockも同様に作成可能です。便利。
mockを自動生成してくれるGoMockも魅力的ですが、今回ご紹介した方法だと柔軟にmockが作れるので良いなあと思います。
testingのみでサクッとmockを作りたい場合にはぜひ試してみて下さい。