3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Go言語】mockの生成からDIを一箇所にまとめて単体テストの記述をちょっと省エネにした

Last updated at Posted at 2024-12-05

この記事は、tacoms Advent Calendar 2024の5日目です!
他メンバーのAdvent Calendarはこちらからご覧ください!👇

背景

mock使っていますか?
単体テストにおいてmockを活用するのは一般的ですが、テストケースごとにmock生成しDIするのは冗長になりがちで、可読性も良くありません。

例えば、以下のような具合です。(弊社ではmockeryというライブラリを使っています。)

task_test.go
func TestCreateTask(t *testing.T) {
    // userのmock初期化
    userMock := mock.NewUser(t)
    userMock.Expect().GetUser(ctx, "1").Return(&User{}, nil)

    // userGroupのmock初期化
    userGroupMock := mock.NewUserGroup(t)
    userGroupMock.Expect().ListUserGroups(ctx, []string{"2", "3"}).Return([]*UserGroup{}, nil)

    // taskのmock初期化
    taskGroupMock := mock.NewUserGroup(t)
    taskGroupMock.Expect().Create(ctx, &Task{}).Return(nil)

    // mockを注入
	server := &Server{
		User:      userMock,
		UserGroup: userGroupMock,
		Task:      taskGroupMock,
	}
	err := server.CreateTask(ctx, "some task")
	assert.NoError(t, err)
}

このように、生成する対象のmockが多ければ多いほど初期化がつらくなってきます。
また、生成したmockはテスト対象の構造体にDIしなければならないため、そのコード自体も冗長化しがちです。

解決方法

mock生成をまとめる関数の導入

以下のように、mockの生成からDIを一箇所でまとめてやってみます。
これによってテストケース毎にmockを生成してDIするコードを書く手間が省けます。
また、新しいmockが必要になった時は、誰かがそれを追加しさえすれば、他の人は利用するだけで済みます。

server_test.go
type mock struct {
	user      *mock.MockUser
	userGroup *mock.MockUserGroup
	task      *mock.MockTask
	// and so on
}

func newMock(t *testing.T) (*server, *mock) {
	m := &mock{
		user:      persistencemock.NewMockUser(t),
		userGroup: persistencemock.NewMockUserGroup(t),
		task:      persistencemock.NewMockTask(t),
		// and so on
	}
	s := &server{
		User:      m.user,
		UserGroup: m.userGroup,
		Task:      m.task,
		// and so on
	}
	return s, m
}

実際のテストでの利用

使う側はnewMock関数を呼び出すだけです。
本記事の一番上で例に出した冗長なコードよりもスッキリしたのではないでしょうか?

task_test.go
func TestCreateTask(t *testing.T) {
    // mockをまとめて初期化しDI
    server, mock := newMock(t)
    
    mock.user.Expect().GetUser(ctx, "1").Return(&User{}, nil)
    mock.userGroup.Expect().ListUserGroups(ctx, []string{"2", "3"}).Return([]*UserGroup{}, nil)
    mock.task.Expect().Create(ctx, &Task{}).Return(nil)

	err := server.CreateTask(ctx, "some task")
	assert.NoError(t, err)
}

まとめ

今回のアプローチにより、以下のメリットが得られました。

  • テストコードの可読性とメンテナンス性が向上した
  • mockの追加や変更が容易になった
  • テストロジックに集中できる環境が整備された
  • 書きっぷりの統一された

今回の例は、導入コストが低く、すぐに実践できる方法を紹介しました。しかし、他にもDI系ライブラリや、テスト用フレームワークを活用することでさらに効率化することも可能かと思います。

プロジェクトの規模や要件に応じて、最適なアプローチを選ぶことが重要です。

PR

現在株式会社 tacoms では全ポジションで絶賛採用募集中です!
是非気軽にカジュアル面談からお話ししましょう 👏

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?