eviry tech & service blog

「株式会社エビリー」の社員ブログです。弊社では、クラウド型動画配信サービス「millvi」、ソーシャル動画データ及び分析サービス「kamui tracker」、YouTube総合メディア「かむなび」を開発・提供しています。https://eviry.com/

gomockでモックを使ったテストを実装する。

これは旧eviry tech blogから移行した記事です。

eviry開発のtkです。

今回はGo関連のテスト用ライブラリである「gomock」を使ってみたので、その使い方などを共有します。

GitHub - golang/mock: GoMock is a mocking framework for the Go programming language.

gomockを使うと、いわゆるモックを定義してテストが実装できるようになり、テスト対象でないクラスの内部実装を意識しなくて良くなるので、テストが書きやすくなります。
ただ、wire同様にgomockもモック定義ファイルを生成する必要があるので、ちょっと手間がかかるのが難点です。

まずはテスト対象となるクラスと、これが使う別のクラスをそれぞれ定義します。
今回はこれらをSampleService, SampleRepositoryとし、Sampleというモデルを取得する機能を実装してみます。

// model/sample.go
package model

type Sample struct {
    Id int
    Text string
}
// repository/sample_repository.go

package repository

import "gomock-test/model"

type ISampleRepository interface {
    GetSample() model.Sample
}

type SampleRepository struct {}

func (sr SampleRepository) GetSample(type ) model.Sample {
    return model.Sample{Id: 1, Text: "sample"}
}
// service/sample_service.go
package service

import (
    "gomock-test/model"
    "gomock-test/repository"
)

type SampleService struct {
    isr repository.ISampleRepository
}

func (ss SampleService) GetSample() model.Sample {
    return ss.isr.GetSample()
}

ここでSampleServiceのGetSampleメソッドのテストをモックを使って実装してみます。

まずはgomockを取得します。
このとき、mockgenコマンドも一緒にインストールしておきましょう。

go get github.com/golang/mock/gomock
go install github.com/golang/mock/mockgen

次に、mockgenコマンドを使ってモック定義ファイルを作ります。
sourceオプションにモックの元になるファイル、destinationオプションに出力先を指定します。

mockgen -source repository/sample_repository.go -destination mock/sample_repository_mock.go

これを使ってテストを実装します。
モックを使うためには、まずモック用のコントローラーを定義し、これをモックの初期化関数の引数として渡します。
モックの初期化関数はmockgenで生成されたファイルの中で定義されています。
あとは出来上がったモックオブジェクトの振る舞いを実装すれば、テスト実行時にその通りに動いてくれます。

// service/sample_service_test.go
package service

import (
    "github.com/golang/mock/gomock"
    "gomock-test/mock"
    "gomock-test/model"
    "testing"
)

func TestGetSample(t *testing.T) {
    mockCtrl := gomock.NewController(t)
    defer mockCtrl.Finish()

    expected := model.Sample{}

    sampleRepository := mock_repository.NewMockISampleRepository(mockCtrl)
    sampleRepository.EXPECT().GetSample().Return(expected)

    sampleService := SampleService{isr: sampleRepository}
    actual := sampleService.GetSample()

    if actual != expected {
        t.Errorf("Actual sample is not same as expected")
    }
}

今回のように依存する先のメソッドが簡素な場合にはモックを導入するメリットはあまりないかもしれません。
ただし、関数が複雑になっていったり依存する対象が増えていったりすると、急速にテストは実装しにくくなってきます。
また、依存先のメソッドの内部実装を意識したテストを書かなければならなくなりますが、モックを使えばそういった問題から開放されます。

gomockは他にも有用な関数を多数持っています。
順番を制御したり呼び出し回数を監視するなど、モックとして必要な関数は一通り揃っていると思います。
私も、まずはこのライブラリを使ってテストを実装していこうと考えています。

以上になります。