Go のテストを書く時、期待値として構造体を比較したいときが多く発生すると思います。
特に時刻情報などテストで扱う場合、テストを書くレベルが高くなるかと思います。
そんな時におすすめなのが go-cmp になります。
例えば以下のような構造体がある場合、
type User struct { ID uint64 Nmae string CreateAt time.Time UpdateAt time.Time}
go-cmp を使わないでテストを書く場合、このようになる。
func TestNoGoCmpUser(t *testing.T) { user1 := User{ ID: 1, Name: "taro", CreateAt: time.Now(), } user2 := User{ ID: 1, Name: "taro", CreateAt: time.Now(), }
if user1.ID != user2.ID { t.Errorf("user1 %v, but user2 %v", user1.ID, user2.ID) }
if user1.Name != user2.Name { t.Errorf("user1 %v, but user2 %v", user1.Name, user2.Name) }}
この場合`CreateAt`など時刻を比較する場合エラーになってしまう。
そのような問題を解決するときに、`go-cmp`を使うと解決できる。
go-cmp を使うメリットとしては以下の場合があるかと思います。
値に Diff があった場合に見やすい
比較対象を柔軟に制御しやすい
ちなみに Go のテストでは、reflect.DeepEqual
がありますが、
こちらの go-cmp は上位互換に位置しているものです。
この場合、CreateAt
UpdateAt
を除外すると以下のように記述できます。
func TestUser(t *testing.T) {
user1 := User{ ID: 1, Name: "taro", CreateAt: time.Now(), }
user2 := User{ ID: 1, Name: "taro", CreateAt: time.Now(), }
opt := cmpopts.IgnoreFields(User{}, "CreateAt", "UpdateAt")
if diff := cmp.Diff(user1, user2, opt); diff != "" { t.Errorf("X value is mismatch (-num1 +num2):%s\n", diff) }}
cmpopts.IgnoreFields
に除外したい値を入れることで除外できます。
先ほどは時刻の値をチェックしていましたが、もう少し緩くチェックしたい場合もあると思います。
そんな時は、cmpopts.EquateApproxTime
を使用します。
func TestUser1(t *testing.T) {
user1 := User{ ID: 1, Name: "taro", CreateAt: time.Now().Add(time.Duration(1) * time.Second), }
user2 := User{ ID: 1, Name: "taro", CreateAt: time.Now().Add(time.Duration(2) * time.Second), }
// エラー // output // ID: 1, // Name: "taro", // - CreateAt: s"2022-09-19 20:51:58.980243 +0900 JST m=+1.003101001", // + CreateAt: s"2022-09-19 20:51:59.980243 +0900 JST m=+2.003101167", // UpdateAt: s"0001-01-01 00:00:00 +0000 UTC", // } if diff := cmp.Diff(user1, user2); diff != "" { t.Errorf("X value is mismatch (-num1 +num2):%s\n", diff) }
if diff := cmp.Diff(user1, user2, cmpopts.EquateApproxTime(5*time.Second)); diff != "" { t.Errorf("error case:\n(-got +want)\n%v", diff) }}