Go - ユニテ環境を爆速で導入!
「Goアプリにユニットテスト環境を爆速で導入し爆速で実装!」たったの3ステップで!
さっそく
Step1 "gomockをインストール!"
以下のコマンドを実行しましょう(今回は v1.6.0
を使います)
% go install github.com/golang/mock/mockgen@v1.6.0
Step2 "テストコードを(ある程度)自動生成!"
以下のコマンドを実行しましょう(user_repository.go
があるとします)
% mockgen -source=user_repository.go -destination=mock_user_repository.go
以下のコマンドを実行しましょう(user_find_usecase.go
があるとします)
% gotests -w -all user_find_usecase.go
参考
package main
type UserRepository interface {
FindById(id uint) (*User, error)
}
package main
type UserFindUseCase interface {
FindById(id uint) (*User, error)
}
type userFindUseCase struct {
userRepository UserRepository
}
func (r *userFindUseCase) FindById(id uint) (*User, error) {
...
}
Step3 "テストケースを追加!"
Step2のgotests ...
で生成されたコードを見てみましょう。(一部抜粋)
func Test_userFindUseCase_FindById(t *testing.T) {
type fields strcut { userRepository UserRepository }
type args struct { id uint }
tests := []struct {
name string
fields fields
args args
want (*User, error)
}{
// TODO: Add test cases.
}
for _, tt := range test {
t.Run(tt.name, func(t *testing.T) {
u := &userFindUseCase{
userRepository: tt.fields.userRepository,
}
if got := u.FindById(tt.args.id); !reflect.DeepEqual(got, tt.want) {
t.Errorf("userFindUseCase.FindById() = %v, want %v", got, tt.want)
}
})
}
}
上記が自動生成されたコードです。
// TODO: Add test cases.
と書かれた部分にテストケースの追加と
モックの振る舞いを定義すればテストコードが完成します。やってみましょう。
func Test_userFindUseCase_FindById(t *testing.T) {
type fields strcut { userRepository UserRepository }
type args struct { id uint }
tests := []struct {
name string
fields fields
args args
+ mocks func(ur *MockUserRepository)
want (*User, error)
}{
+ {
+ name: "[case1] success - get user",
+ args: args{
+ id: uint(1)
+ },
+ mocks: func(ur *MockUserRepository) {
+ ur.EXPECT().FindById(uint(1)).Return(&User{id: uint(1)}, nil)
+ },
+ want: &User{id: uint(1)}
+ },
}
for _, tt := range test {
t.Run(tt.name, func(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+ ur := NewMockUserRepository(ctrl)
+ tt.mocks(ur)
u := &userFindUseCase{
+ userRepository: ur,
}
if got := u.FindById(tt.args.id); !reflect.DeepEqual(got, tt.want) {
t.Errorf("userFindUseCase.FindById() = %v, want %v", got, tt.want)
}
})
}
}
上記が、自動生成されたコードに追記・編集したものです。
コード見るだけでなんとなく雰囲気は掴めると思います。(詳しくはこのあと解説します)
これでテストコード完成です。
ちょっとくわしく
いかがでしたか。爆速で導入&実装できたと思います。
詳しく見ていきましょう。
使用しているライブラリ
library | version |
---|---|
gomock | 1.6.0 |
cweill/gotests | 1.6.0 |
gomock
は、名前の通りモック生成してくれるライブラリです。
interface定義されたファイルを指定すると、そのモックを自動生成してくれます。
(step2のmockgen
コマンドでやっていたこと)
cweill/gotests
は、テストコードの実装をサポートしてくれるライブラリです。
テストコードは公式推奨のTableDrivenTests
で生成してくれるため、
我々はテストケースの列挙だけすればOK、という便利なものです。
(step2のgotests
コマンドでやっていたこと)
step3でやっていること
追記・編集した箇所を中心に解説します。
mocks func(ur *MockUserRepository)
モックの振る舞いを定義するために、テストケースの構造体に新しくフィールドを追加しています。
{
name: "[case1] success - get user",
args: args{
id: uint(1)
},
mocks: func(ur *MockUserRepository) {
ur.EXPECT().FindById(uint(1)).Return(&User{id: uint(1)}, nil)
},
want: (&User{id: uint(1)}, nil)
},
これがテストケースの定義です。name
がテスト名、args
がテストパラメータ、mocks
がモックの振る舞い、want
が期待値になります。
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ur := NewMockUserRepository(ctrl)
tt.mocks(ur)
モック呼び出しの管理をするControllerを用意し、実際に必要なモックを生成しています。
NewMockUserRepository()
もmockgen
コマンドで自動生成されたものです。
u := &userFindUseCase{
userRepository: ur,
}
生成したモックをインジェクションしています。
Tips
「実装修正したのにmockコードの更新忘れてたからテストが落ちてる!」
こういうことはGithub ActionsなどのCIで更新させればいい感じになりそうですね。
最後に
ユニットテストは大事ですがその分実装負担もそれなりにありますよね。
でもこれらを使いこなすことである程度の負担軽減ができたかな〜と思います。
※サンプルコードにミスがあればご指摘いただけると幸いです。
Discussion