モックを使ったテストって?
テストのために本物のオブジェクトを使わず、テスト用に差し替える手法です。
モックを用いたテスト自体、否定派・賛成派あるのでいろいろ意見はありますが
階層型のアーキテクチャを選定していると、一番下のレイヤまで気を配らないと
上位層のテストがかけないのはしんどいですしよね。
例えば
- 他社API処理
通信コケてCIガー、実行時間ガー
- データベースがらみ
並列実行すると、コケるワー、実行時間ガー
テストの障害が増えると、テストを書かなくなりますし、メンテナンスも大変に
なっていくので使える所は使った方が良いのかな、と。
一番ストレートなやり方 (例)
goの記事というか基本的なDIの活用例っぽいですが...
-
基本的にinterfaceに依存する。
-
DI(Dependency Injection)を活用する。
-
テスト対象のサンプルコード
package main
import (
"errors"
)
type APIClient interface {
Call(string) (map[string]string, error)
}
type UserRegister struct {
client APIClient // interfaceに依存させる。
}
type User struct {
Name string
}
// 引数に受け取ったnameを元に別APIでuserを登録する
func (u *UserRegister) Register(name string) (*User, error) {
apiResult, err := u.client.Call(name)
if err != nil {
return nil, err
}
resultName, exist := apiResult["name"]
if !exist {
return nil, errors.New("apiResult must have name")
}
return &User{resultName}, nil
}
- テストコード
package main
import (
"testing"
)
// mockする構造体を定義する
type APIClientMock struct {
}
// APIClientInterfaceを満たすようにモックを実装する
func (a *APIClientMock) Call(name string) (map[string]string, error) {
return map[string]string{"name": name}, nil
}
func TestRegister(t *testing.T) {
// Mockを外部注入する
target := &UserRegister{&APIClientMock{}}
expected := "Mike"
user, err := target.Register(expected)
if err != nil {
t.Fatalf("Register error: %v", err)
}
actual := user.Name
if actual != expected {
t.Fatalf("name Must be %s", expected)
}
}
goには継承の概念がない代わりにembedで実装の再利用を可能に
していますが、型判定は通りません。
オブジェクト指向っぽく書く場合はinterfaceを使う方が良いですね。
(goに限らず...な所もありますが)
便利ライブラリ
上記の例では省きましたがより厳密にテストをするのであれば、
UserRegisterの責務である、APIClient.Call()の呼び出しの担保されたかまで考慮しないといけないので、
もう少し実装をかけないといけなくて、それはそれで面倒ですよね。
毎回作るのも面倒臭いし。
他の言語でも大概その手のライブラリはありますし、goにも勿論あります。
こちらのライブラリなら以下のような使い心地で簡単にできます。
//trueで呼ばれたら1を返す
mock.EXPECT().Method(true).Return(1)
// 初回は1,"first"で呼ばれることを保証
firstCall := mock.EXPECT().SomeMethod(1, "first")
// 1で呼ばれた後で2,"second"で呼ばれることを保証
secondCall := mock.EXPECT().SomeMethod(2, "second").After(firstCall)
// 2で呼ばれた後に3,"third"で呼ばれることを保証
mockObj.EXPECT().SomeMethod(3, "third").After(secondCall)
mockgenでmockのgenerateも出来るのでとても便利です :)
関連
- gomock
- [mockgen] (https://godoc.org/github.com/golang/)