ハマったのでメモ
助けられた
大変参考になりました。ありがとうございます
https://blog.rokiyama.dev/2020/09/11/go-cmp-diff-protobuf/
状況
golangでprotobuf+gRPCなサーバーサイドプログラムのテストを書いてる時に発症した。
テストでは、テスト対象の関数に渡す値と、期待される戻り値を最初に定義しておき、その2つの値(入・出)をgo-cmpで比較する方式を取っていた。👆のリンクではdiffを取っている例だけど、私の場合はEqualで再現した。
panic: cannot handle unexported field at {*package.StructName}.ChildStructName.state:
"github.com/hoge/mypb/go/package/v1".Struct
結局protogenで生成したprotobuf messageのStruct内において、外部に出てない自動生成フィールド(小文字で定義されたもの)を比較できないことによって発生している模様。
type Name struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
}
panicにも出てるように、この型のstateフィールドが引っかかってるらしい。
対策1
ブログにたどり着き、若干状況違う中で下記の通りテストを書き直したらpanicしなくなった
import(
"google.golang.org/protobuf/testing/protocmp" // 追加でimportしておく
)
if !cmp.Equal(res, expectedResponse, opts, protocmp.Transform()) { // Equalに渡すパラメータに protocmp.Transform() を追加
t.Fatalf("got %v want %v", res, expectedResponse)
}
対策2
go-cmpのオプション IgnoreUnexported
を活用する
名の通り外に出てないフィールドを無視するための関数が用意されている
opts := cmpopts.IgnoreUnexported(package.ParentStruct{}, package.RelatedStruct1{}, package.RelatedStruct2{})
if !cmp.Equal(res, expectedResponse, opts) {
t.Fatalf("got %v want %v", res, expectedResponse)
}
比較したいstructにおいて、unexportedなfieldが入っているものを全て渡しておくと良い。protocmp.Transform()に比べて、必要な分だけ無視できる良さがあるが関連するstructが多くなると列挙するのが面倒になってくる