はじめに
2024/10/18*1にgomock (mockgen) v0.5.0がリリースされました。
github.com
リリースノートを見ていこうと思うのですが、その前にpackage modeという新しいコード生成のモードについて触れましょう。
reflect modeの限界とpackage modeについて
mockgen v0.4.0までは、コード生成のモードとしてsource modeとreflect modeの2つがありました。
source modeは、指定されたGoのソースファイルを構文解析して得られた情報もとにinterfaceのモック生成を行うモードです。モック生成の対象がファイル単位でしか指定できない・別のpackageのinterfaceモックが生成できない など制約は強いけど、静的解析だけで済むので高速にコード生成できるモードです。このモードはmockgen v0.5.0にも残っています。
reflect modeは、リフレクションを使ってinterfaceのモック生成を行うモードです。リフレクションが使えるのでsource modeより柔軟性が高いように見えて、じつはいくつか限界がありました。
代表的なものに、ジェネリックな型を返すメソッドを含むモックのコード生成がエラーになる、というものがあります。reflect modeで以下のようなinterfaceのモックを生成するとエラーになります。
github.com
type Interface interface {
GetSomeInt() *Some[int]
}
type Some[T any] struct {
}
ほかにもいくつか限界がありました*2が、これらを解決するためにreflect modeとは仕組みの異なるコード生成モードを導入した、というのがpackage modeのモチベーションになります。
package modeは、golang.org/x/tools/go/packagesを使ってコードの解析を行ってinterfaceのモックを生成するモードになります。mockgen v0.5.0で導入されました。
github.com
package modeはreflect modeとの互換性を保ちつつ、reflect modeでは解決できてなかったコード生成の問題も解決しています。リフレクションではなく、golang.org/x/tools/go/packages というパッケージで提供されるGoのコード解析の仕組みを使っています。golang.org/x/tools/go/packages については以下の記事が詳しいです。
int128.hatenablog.com
リリースノート
package modeについて見たので、リリースノートのほうも見てみましょう。
Added
github.com
mockgenでコード生成をすると、生成されたコードに以下のようなコメントが出力されます。
この挙動はv0.4.0まではコマンドライン引数で制御できなかったのですが、v0.5.0ではこのコメントを出力しないように --write_command_comment=false
コマンドライン引数で設定できるようになりました。
コード生成時に実行されるコマンドは環境依存で、生成されるコードに余計な差分が入ってしまうから無効にできるようにしよう、というのが元のPRのモチベーションです。ワイトもそう思います。
bulkmockgenを作った側としては、モックするinterfaceを追加したときにこのコメントがコンフリクトして困っていました。コード生成後にこのコメントを取り除くスクリプトを回すようにしてなんとかしていましたが、コマンドライン引数が導入されたことでスクリプトの用もなくなって嬉しいですね。
#191: Add --build_constraint
flag to add //go:build
directives to generated mocks
github.com
生成されたコードに対して --build_constraint
コマンドライン引数でbuild constraintを指定できるようにした、という変更です。
mockgenで生成したコードは基本的にテストでしか使わないけど、パッケージが分かれているのでプロダクションコードからimportしてしまう可能性がある、なのでbuild constraintで保護できるようにした、というモチベーションのようです。
github.com
たまに思うけど、テストを走らせるときだけ有効になるbuild tagとかないのかな。
#214: Add gob mode to support custom package loading techniques in place of --exec_only
github.com
reflect modeがpackage modeに代替されたことで、interfaceの情報を得るためにコードをコンパイルして実行する必要がなくなりました。そのため --exec_only
コマンドライン引数が削除されたのですが、これだと困る場合があるようです。mockgenによるコード生成結果をうまく制御するために、mockgenのinterfaceの解析結果の内部表現をgob encodingしたものを得て、コネコネしたのちmockgenに渡す、ということが行われていたようです。ここまでやったことがないのでまだモチベーションが掴みきれていません。
この問題を解決するために、--model_gob
というコマンドライン引数経由で、内部表現をgob encodingしたもののファイルパスを渡せるようにした、ということのようです。
Changed
#181: Made mockgen faster by changing flags passed to go list
.
github.com
mockgenでコード生成するときに go list -json
コマンドが叩かれていますが、引数を調整することでコード生成を高速にした、という変更です。
go list -json
だけだと依存パッケージの解析なども行われますが、必要なのは ImportPath
と Name
だけなので無駄だった、ということですね。
#183: Made Cond
matcher generic.
github.com
Cond
matcherがジェネリックになりました。godocの差分*3を見るのが一番分かりやすいでしょう。
Cond(func(x any){return x.(int) == 1}).Matches(1)
Cond(func(x any){return x.(int) == 2}).Matches(1)
Cond(func(x int){return x == 1}).Matches(1)
Cond(func(x int){return x == 2}).Matches(1)
ジェネリックでない既存のコードは型引数が常に any
型に推論されたものだとみなせるので、互換性は保たれています。
#204: Removed ISGOMOCK()
from generated mocks.
github.com
生成されるモックに ISGOMOCK()
メソッドを生やすのではなく、isgomock
というプライベートなフィールドを含めるようにする、という変更です。この差分だけ見てもモチベーションが一見分かりづらいと思いますが、#144 と併せて見ることで謎が解けます。
#207: Deprecated reflect mode and replaced it with the new package mode.
github.com
reflect modeに対応するモードをまったく別の仕組みで書き直して、package modeという名前で導入した、という変更です。
記事の冒頭で触れたように、これが今回のリリースで一番の目玉じゃないでしょうか。
Fixed
#144: Fix a deadlock that can happen when mocking an interface that matches fmt.Stringer
.
github.com
fmt.Stringer
interfaceに対するモックがdeadlockを引き起こしうる問題が解決されました。
fmt.Stringer
の String()
メソッドがモックされていると、モック実装を文字列化する → String()
メソッドが呼ばれる → モック実装が適切に呼ばれているかどうかの判定が走る → …… という無限ループになる、ということですかね。さもありなん……。
解決策として、モック実装を文字列化するときは %v
ではなく %T
というフォーマットで文字列化する、という形を取っています。モック実装かどうかを判定するために ISGOMOCK()
メソッドの存在を確認していましたが、これだと名前衝突が起こる可能性が残っています。ということで、メソッドではなくプライベートなフィールドを使って判定するように変更した、というのが #204 なんですね。
github.com
パッケージ単位のコメントとして // Code generated by MockGen. DO NOT EDIT.
から始まる行を含めないようにする、という変更です。差分は非常にシンプルですね。
おわりに
より便利になり、package modeの導入によって限界も超えたgomock (mockgen) v0.5.0がリリースされました。みなさまもどんどん最新バージョンを使っていきましょう。