Overview
Go Test においてテスト対象の出力結果と期待する値を比較する手法として reflect.DeepEqual
ではなく go-cmp を使うことが多くなっていると思いますが、この go-cmp
の cmp.Option
が可変長引数として Diff に指定することができるところで、使い方と可変長引数の指定の仕方でちょっとハマったので、思い出し作業もかねて備忘録としてまとめました。
go-cmp の cmp.Option
比較する時に幾つかの Option を追加することができます。
追加できるオプションは例えば以下のような比較する struct に含まれる非公開フィールドを無視するcmpopts.IgunoreUnexported
や特定のフィールドを比較対象から外す cmpopts.IgnoreField
があります*。
*CreatedAt や UpdatedAt のような時刻フィールドは時間が絡むので意図せず壊れやすいため最初から比較対象として外すことが多いです。もちろん時刻が重要なテストでは外してはいけません。
How to use cmp.Option
Diff で可変長引数に指定されている opts に cmp.Option を割り当てることができます。
これの割り当て方はいくつかあります。
例えば cmpopts.IgnoreUnexported
を例に取ると https://github.com/google/go-cmp/blob/master/cmp/cmpopts/ignore.go#L119 のように無視したい非公開フィールドを持つ struct を複数登録することが可能です。(引数に可変長引数を取っているため)
cmp.Options の中に option で指定したい設定を入れる
opts := cmp.Options{ cmpopts.IgnoreUnexported(A{}), } if diff := cmp.Diff(x, y, opts); diff != "" { fmt.Printf("mismatch (-x +y):\n%s", diff) }
ref: https://play.golang.org/p/YrjDcmT6P9J
これは素直な実装です。Diff の可変長引数に指定されている ...cmp.Option
は呼び出し側の Diff メソッドの内部では cmp.Option
の配列として扱われるので、type Options []Option
と定義されている (ref: https://pkg.go.dev/github.com/google/go-cmp/cmp#Options) と定義されている cmp.Options
はそのまま cmp.Option
の配列として Diff の引数に指定することができます。
cmp.Option 単体を指定する
opts := cmpopts.IgnoreUnexported(A{}) if diff := cmp.Diff(x, y, opts); diff != "" { fmt.Printf("mismatch (-x +y):\n%s", diff) }
ref: https://play.golang.org/p/9rKklLLQlQG
可変長引数が呼び出し側で配列(slice) に展開される、ということを考えると若干直感的ではないですが、可変長引数なので単体の cmp.Option
を受け取ることも可能です。
slice にして指定する
opts := []cmp.Option{cmpopts.IgnoreUnexported(A{})} if diff := cmp.Diff(x, y, opts...); diff != "" { fmt.Printf("mismatch (-x +y):\n%s", diff) }
ref: https://play.golang.org/p/-qbaKbwWI42
type Options []Option
と定義されている && Diff の内部では opts は slice に展開されて利用されるということを考えると、指定時に cmp.Option
の slice にしてしまって可変長引数を指定することも可能です。この場合 slice を可変長引数に指定するので $slice...
という形で渡す必要があります。
Go の可変長引数の指定方法について
Go の関数の引数に可変長引数を指定した場合、以下のサンプルに示した3つのパターンで可変長引数を指定することが可能です。
サンプルコードだと 2 のパターンは直感的にはわかりやすいですが、3 のパターンは Go に慣れてないととっつきづらいかもしれません。
type str string func main() { m("1") m("1", "2") m([]str{"1"}...) } func m(s ...str) { fmt.Println(s) }
ref: https://play.golang.org/p/7N75PyB2wBW
cmp.Options
をそのまま Diff に指定できるわけ
可変長引数を指定する場合 slice 型にした配列でも ...
で展開指定しないと可変長引数には実は指定できません。
type strs []str type str string func main() { m(strs{"1"}...) } func m(s ...str) { fmt.Println(s) }
ref: https://play.golang.org/p/pAlGE6ZZcH1
しかし cmp.Options はそのままでも引数に指定することができます。
※ 実は ...
を指定することも可能です。
opts := cmp.Options{ cmpopts.IgnoreUnexported(A{}), } if diff := cmp.Diff(x, y, opts...); diff != "" { // ... を指定してる。 fmt.Printf("mismatch (-x +y):\n%s", diff) }
ref: https://play.golang.org/p/Rq4nL01RNXP
cmp.Options
が ...
で展開指定せずとも、可変長引数に指定可能なのは、cmp.Options
は cmp.Option
の slice でありながら、Option として振る舞うことができる ( filter(s *state, t reflect.Type, vx, vy reflect.Value)
を実装してる)からです。
Option の slice でありながら、Option 型と同等に振る舞うことができるので、上記の cmp.Option 単体を指定する
と同様のことが 型上許容される という挙動をするようです。
まとめ
よく使っていたライブラリも、ちょっと使う期間が空いてしまうとすぐに使い方を忘れますし、実装方法をちょっと深ぼってみると面白いですね。