インターンとしてメルカリアプリのホーム画面や検索のバックエンド開発に携わっている@sanposhihoです。
この記事ではgolang/mock(以下gomock)のコード生成をgo generate
コマンドで行うことに起因する課題点と、それを解決するために作ったgomockhandlerの紹介をします。
https://github.com/sanposhiho/gomockhandler
TL;DR
go generate
コマンドによってgomockを利用していると、「大量のモックの生成に時間がかかる」「モックが最新のインターフェースに沿って生成されているか確認できない」などの課題が生じます。
これを解決する、gomockhandlerというツールを作成しました。gomockhandlerを使用することで、多くのモックを一度に高速に生成することやモックが最新であることのチェックを行うことができます。
gomockとは
gomockとはインターフェースの定義からモックの生成を行うGo公式のツールです。
https://github.com/golang/mock
弊社でも利用しているプロジェクトは多くあります。
gomockは以下のようにmockgen
コマンドを実行することでモックが生成されます。
mockgen -source=foo.go -destination=../mock/
従来のgo generate
を用いたモック管理
前述のようにモックはインタフェース定義を元に生成されます。そのため定義を変更した場合はもう一度同じコマンドでモックを生成し直す必要があります。
また、1つのコマンドにつき1つのモックファイルが生成されるため、数が多い場合に管理が煩雑になっていきます。
そこでモックの生成によく用いられるのがgo generate
コマンドです。
//go:generate mockgen -source=foo.go -destination=../mock/
go:generate
コメントディレクティブを各インタフェースが定義されているGoファイルに書いておき、go generate ./...
を実行することで同時にプロジェクト全体のモックを最新のインタフェース定義をもとに更新できます。
この管理方法は便利ですが、いくつかの問題に直面します。
go generate
コマンドでモックを管理することの課題
mockの生成を並列に行うことができない
go generate
コマンドはその特性上全てのファイルを走査し//go:generate
と書かれたコメントを探し1つずつ実行していくようにデザインされています。 これは実行の順番を保証することで複数のgo:generate
コマンドを通して、一連の動作を行うことを可能にするためです。
そのため並列にモックを生成し、実行時間を縮めるということができません。
モックが最新のインタフェースに追従しているかの確認ができない
これによって、
- 古いインタフェースの定義から生成したモックを使用したテストを書いてしまう
- 消したはずのインタフェースを元にしてたモックがリポジトリに残ってしまう
という問題が起こります。
古い定義に沿ったモックは不適切なテストを書く原因になってしまうかもしれませんし、消えていてほしいモックが残っているとこれも誤用やプロジェクト理解のコストに余計な負荷がかかってしまうかもしれません。
本来であればCIなどでインターフェースの更新時にモックの更新を行なったかをチェックし、常に最新のモックが置かれている状態を担保したいところです。
モック生成のコマンドの定義が散らばる
モックの生成を行うためのコメントが各ソースコードに散らばります。
プロジェクト全体を通して、どのファイルにgo:generate
コメントディレクティブを記載するかやmockgen
のオプションの指定などを統一するのは難しいです。
gomockhandlerによる問題の解決
これらの問題を解決するgomockhandlerを開発しました。
https://github.com/sanposhiho/gomockhandler
以下の特徴があります
- モックの管理を1つの設定ファイルのみを通して行う
- 並列にモックの生成を行う
- モックが最新のインタフェースを元に生成されているかを確認できる
- CLIによって設定ファイルの変更を行う
go generate
コマンドを使用している既存のプロジェクトでも管理を移行しやすい
1つずつ解説していきます(現時点での最新はv1.1.1です)
モックの管理を1つの設定ファイルのみを通して行う
これによってモックの管理を散らばったコメントの代わりに、一つのファイル内に統一されたフォーマットで行うことができるようになります。
また、ファイル内にすべてのモックの生成方法が記載されているため、他のモックの生成がどのように行われているかを確認しやすくなりモックの生成の方法を統一しやすくなります。
並列にモックの生成を行う
gomockhandlerでは並列にモックの生成を行うのでgo generate
と比べて全体のモックの生成が早くなります。
configオプションにてconfigへのパスを指定できます。指定しなかった場合は./gomockhandler.json
を使用します。
gomockhandler [-config=/path/to/config.json] mockgen
実際に社内のプロジェクトで試してみると
13.1秒→5.4秒(60%減)
1分30秒→27秒(70%減)
このように大幅に時間を短縮することができています。
モックが最新のインタフェースを元に生成されているかを確認できる
gomockhandlerではモックが最新のインタフェースを元に生成されているかを確認できます。
gomockhandler [-config=/path/to/config.json] check
これによってCIなどでモックが最新の状態になっているかを確認することができます。
CLIによって設定ファイルの変更を行う
設定ファイルに関しては勿論手動でも編集することができますが、CLIによる編集を推奨しています。
- 生成するモックの登録/更新
- 生成するモックの削除
がCLIから行えます。
登録や更新にはそのままgomockと全く同じオプションを使用し、mockgen
コマンドが正しく実行されるオプションであるかの確認が同時に行われます。
例えば、以下のmockgenコマンドで生成されるmockを登録したい場合
mockgen -source=foo.go -destination=../mock/
以下のgomockhandlerコマンドで設定にモックの登録を行うことができます。
gomockhandler [-config=/path/to/config.json] -source=foo.go -destination=../mock/
また、生成するモックの削除は以下のように行います。
gomockhandler [-config=/path/to/config.json] -destination=./mock/user.go deletemock
モックの生成と同様にconfigオプションを指定しなかった場合は./gomockhandler.json
を使用します。
go generate
コマンドを使用している既存のプロジェクトでも管理を移行しやすい
既存のgo generate
を使用しているプロジェクトから簡単にgomockhandlerに移行できます。
- //go:generate mockgen -source=$GOFILE -destination=mock_$GOFILE -package=$GOPACKAGE
+ //go:generate gomockhandler -config=/path/to/config.json -source=$GOFILE -destination=mock_$GOFILE -package=$GOPACKAGE
このようにmockgen
をそのままgomockhandler -config=/path/to/config.json
に置き換えて、
go generate ./...
を実行することで全てのmockの設定が一つの設定ファイルに記載されます。これでモックを生成するgo:generate
コマンドは必要なくなったのですべて削除しましょう。
また、この際さまざまな位置にファイルがあると思うのでconfigは絶対パスで指定してしまうのが良いと思います。(すぐ消すことになるので)
コメントの置換は最近のエディタであればシュッとできると思います。以下にsedを用いて置換を行う例を示します。
grep -lr "mockgen" ./* | xargs sed -i '' 's/mockgen/gomockhandler -config=\/path\/to\/config/g'
終わりに
この記事ではgomockの管理の辛さを解消するgomockhandlerを紹介しました。現状でgomockに特化したモックの管理ツールというのは他に存在していないように認識しています。
バグ報告やfeature requestなどは是非GitHubで気軽にissueを投げてください。starも頂ければ大きな活力になります🙏
みなさん良いgomockライフを!