TAKEO

TAKEO

GoMockを使って単体テストしよう - Go

GoMockの基本と効果的な使い方

GoMockは、Go言語での単体テストのためのモックフレームワークです。テスト対象のコードが外部依存(インターフェイス)を持つ場合に、その依存を模倣するモックを作成することで、テストを簡潔かつ柔軟に行えるようにします。本記事では、GoMockの概要、使い方、そしてプロジェクトでの効果的な活用方法について解説します。


GoMockとは?

GoMockはGoogleが開発したGo用のモックライブラリです。以下の特徴があります:

  • 柔軟なモック生成:インターフェイスをベースにしたモックを自動生成。
  • 期待値の定義:モックの動作や返り値、呼び出し回数などの期待値を詳細に設定可能。
  • 高い統合性:Go標準のテストパッケージとシームレスに統合。

簡単な使用例

Go インターフェース 単体テスト編の記事で紹介したテストをGoMockを使って、書き換えてみます

書き換え前後の差分は次の通りです。自前でテスト用の構造体を定義する必要がなくなったのでテストがスッキリしているのがわかると思います。

Copied!!
$ git diff 6e6595f16013b6c3f6015d4b72fe587a73d445e9..10a47eb41189f2e0bc321ec92c9aecc8c102f3b1 pkg/runner/random_joke_runner_test.go
diff --git a/pkg/runner/random_joke_runner_test.go b/pkg/runner/random_joke_runner_test.go
index 169c030..04a78fd 100644
--- a/pkg/runner/random_joke_runner_test.go
+++ b/pkg/runner/random_joke_runner_test.go
@@ -4,36 +4,27 @@ import (
        "errors"
        "testing"

+       "github.com/golang/mock/gomock"
        "github.com/stretchr/testify/assert"
        "github.com/take-o20/go-cli-example/pkg/client"
 )

-type mockHttpClient struct {
-       mockResponse string
-       mockError    error
-}
-
-func (m *mockHttpClient) Get(string) (string, error) {
-       return m.mockResponse, m.mockError
-}
-
-func createMockHttpClinet(t *testing.T, mockResponse string, mockError error) client.HttpClient {
-       t.Helper()
-       return &mockHttpClient{
-               mockResponse: mockResponse,
-               mockError:    mockError,
-       }
-}
-
 func TestRandomJokeRunner_Succeeded(t *testing.T) {
-       testResponse := `
+       mockResponse := `
 {
   "type": "programming",
   "setup": "Knock-knock.",
   "punchline": "A race condition. Who is there?",
   "id": 362
 }`
-       mockClient := createMockHttpClinet(t, testResponse, nil)
+       ctrl := gomock.NewController(t)
+       defer ctrl.Finish()
+
+       mockClient := client.NewMockHttpClient(ctrl)
+       mockClient.EXPECT().
+               Get("https://official-joke-api.appspot.com/jokes/random").
+               Return(mockResponse, nil)
+
        runner := RandomJokeRunner{
                client: mockClient,
        }
@@ -42,9 +33,17 @@ func TestRandomJokeRunner_Succeeded(t *testing.T) {
 }

 func TestRandomJokeRunner_Failed(t *testing.T) {
-       testResponse := ""
+       mockResponse := ""
        mockErr := errors.New("mock error")
-       mockClient := createMockHttpClinet(t, testResponse, mockErr)
+
+       ctrl := gomock.NewController(t)
+       defer ctrl.Finish()
+
+       mockClient := client.NewMockHttpClient(ctrl)
+       mockClient.EXPECT().
+               Get("https://official-joke-api.appspot.com/jokes/random").
+               Return(mockResponse, mockErr)
+
        runner := RandomJokeRunner{
                client: mockClient,
        }

テストのポイント

  1. gomock.NewController: モックの管理をするためのコントローラーを作成。
  2. mockClient.EXPECT(): モックの期待値を設定。ここではGetが引数https://official-joke-api.appspot.com/jokes/randomで呼ばれると特定の値を返すことを定義。
  3. アサーション: 結果が期待通りであることを検証。

GoMockを使うメリット

  1. 依存関係の排除 外部依存をモックで置き換えることで、テスト対象のコードにのみ焦点を当てられます。

  2. 柔軟なシナリオ構築 モックの振る舞いを詳細に定義できるため、エッジケースや例外処理のテストが容易です。

  3. テストの信頼性向上 外部サービスやリソースに依存しないため、テストの安定性が向上します。


よくある課題と対処法

1. モック生成の煩雑さ

大規模プロジェクトではインターフェイスが多く、モック生成が手間になることがあります。これにはMakefileやスクリプトで自動化を導入するのがおすすめです。

2. 過度なモック依存

モックに依存しすぎると、実際の環境との差異が問題になることがあります。本番環境に近い統合テストと組み合わせてバランスを取ることが重要です。


まとめ

GoMockは、Goのテストをより効率的で信頼性の高いものにする強力なツールです。インターフェイスを活用するGoの設計と相性が良く、柔軟なテストシナリオを構築できます。

初めて使う場合は、小規模なモジュールで試してみるとそのメリットを実感できるでしょう。プロジェクト全体で活用する際には、自動化やコード管理の工夫も取り入れてみてください。


GoMockを導入して、より信頼性の高いテスト環境を構築しましょう!