はじめに
最近GoでMockを用いてテストを行うことが多いのですが,その手法がシンプルで使いやすいので紹介したいと思います.
実装
今回はDBからユーザーを全件取得して,指定したユーザーの年齢以上のユーザーのみを抜き取るという処理に対するテストを考えます.(そんなことはDB側で処理すればいいですが,いい例が思いつかなかったので悪しからず)
まずはUserのModel定義をします
type User struct {
ID int
Name string
Age int
}
次にDBに関する実装を行いますが,interfaceとその具体実装の2つに分けて書きます.
type UserDB interface {
GetAllUsers() ([]User, error)
}
type UserDBImpl struct {
}
func (db *UserDBImpl) GetAllUsers() ([]User, error) {
// 全Userを返す処理
}
ここでUserDBはinterfaceで,UserDBImplはその具体的な実装をする構造体となっています.今回は全てのユーザーを取得する関数であるGetAllUsersをUserDBImplのレシーバとして定義しています.本番のサービスで用いる実際のコード(ORMを用いて実際のデータを取得してくるなど)はこのGetAllUsersに定義します.
次に,このUserDBを用いて年齢によってユーザーを絞り込む処理を実装します.
type UserHandler struct {
DB UserDB
}
func (handler *UserHandler) FilterUsers(age int) ([]User, error) {
allUsers, err := handler.DB.GetAllUsers()
if err != nil {
return []User{}, err
}
filteredUsers := []User{}
for _, user := range allUsers {
if user.Age >= age {
filteredUsers = append(filteredUsers, user)
}
}
return filteredUsers, nil
}
そんなに難しい処理は書いていないので詳細は省きますが, 重要な部分はUserHandlerがinterfaceであるUserDB
をフィールドに持っている点です.これによってUserHandlerのDBにはUserDBで実装しているメソッドと同じメソッドを実装している構造体をなんでも入れることができるようになります(UserDBImplもその1つ).
最後に,このFilterUsersに関するテストを書いていくことにします.
import (
"reflect"
"testing"
)
type UserDBImplMock struct {
}
func (db *UserDBImplMock) GetAllUsers() ([]User, error) {
users := []User{
User{
ID: 1,
Name: "user1",
Age: 10,
},
User{
ID: 2,
Name: "user2",
Age: 25,
},
User{
ID: 3,
Name: "user3",
Age: 16,
},
User{
ID: 4,
Name: "user4",
Age: 33,
},
User{
ID: 5,
Name: "user5",
Age: 18,
},
}
return users, nil
}
func TestFilterUsers(t *testing.T) {
mock := UserDBImplMock{}
handler := UserHandler{&mock}
input := 20
expect := []User{
User{
ID: 2,
Name: "user2",
Age: 25,
},
User{
ID: 4,
Name: "user4",
Age: 33,
},
}
output, err := handler.FilterUsers(input)
if err != nil {
t.Error(err.Error())
}
if !reflect.DeepEqual(&output, &expect) {
t.Errorf("output: %+v \n expect: %+v", output, expect)
}
}
UserDBImplMockはUserDBのメソッドを実装したもので,こちらで実装するメソッドでは名前の通りMockデータを返すようにします.実際,GetAllUsersではDBから取得されると期待されるデータを定義し,それを返すようにしています.
実際にテストしたいメソッドであるTestFilterUsers
では,はじめにUserDBImplMockを用いてUserHandlerを初期化しています.これがUserHandlerがinterfaceをフィールドに持っていることの良い点で,これにより実際のDBを考えずともMockデータでテストができるようになるわけです.
さいごに
Goではinterfaceに関してプログラミングを行うことで非常に簡単にMockによるtestを行うことができます.サービスのアーキテクチャによっては重宝するかと思うので,ぜひ試してみてください.