go-cmp を実際に使ってみた

go でテストケース書いていて、 `reflect.DeepEqual` を使っていると `time.Time` の比較なんかで死んでしまいます。
対策を検索すると、 `go-cmp` 使えよ！ っていう記事は見かけるものの、実際にどういうふうに書けばよいのかいまいちわからないし、[Example](https://godoc.org/github.com/google/go-cmp/cmp#pkg-examples)を読んでもうーん？ってなったので、まあこれは一度使ってみましょう。
ということで使ってみた記録です。


## とりあえず diff を取ってみる

```go
package main

import (
	"fmt"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
)

type S1 struct {
	F1 int
}

// 普通に diff を取るとわかりやすい文字列で教えてくれる
func main() {
	v1 := S1{F1: 1}
	v2 := S1{F1: 1}
	v3 := S1{F1: 2}

	if diff := cmp.Diff(v1, v2); diff != "" {
		fmt.Printf("v1 != v2\n%s\n", diff)
	} else {
		fmt.Println("v1 == v2")
	}

	if diff := cmp.Diff(v1, v3); diff != "" {
		fmt.Printf("v1 != v3\n%s\n", diff)
	} else {
		fmt.Println("v1 == v3")
	}
}
```

実行するこうなります。

```
$ go run main.go
v1 == v2
v1 != v3
{main.S1}.F1:
	-: 1
	+: 2
```
良いですね。

## unexported なフィールドと戦う (1)

unexported なフィールドがあるとどうなるか見てみましょう。

```go
type S2 struct {
	F1      int
	private int
}

// unexported なフィールドがある場合は AllowUnexported 等を指定しないと panic になる
func main() {
	v1 := S2{F1: 1, private: 1}
	v2 := S2{F1: 1, private: 1}
	v3 := S2{F1: 1, private: 2}

	if diff := cmp.Diff(v1, v2); diff != "" {
		fmt.Printf("v1 != v2\n%s\n", diff)
	} else {
		fmt.Println("v1 == v2")
	}

	if diff := cmp.Diff(v1, v3); diff != "" {
		fmt.Printf("v1 != v3\n%s\n", diff)
	} else {
		fmt.Println("v1 == v3")
	}
}
```

実行すると…

```
$ go run main.go
panic: cannot handle unexported field: {main.S2}.private
consider using AllowUnexported or cmpopts.IgnoreUnexported

goroutine 1 [running]:
github.com/google/go-cmp/cmp.invalid.apply(0xc420098000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0)
	/Users/ishizuka/go/src/github.com/google/go-cmp/cmp/options.go:208 +0xf5
...
```

なるほど、panicになるのですね。

## unexported なフィールドと戦う (2) panic良くない

panic は流石にアレなので対策しましょう。
比較時に `cmp.AllowUnexported` というオプションを追加すると良いです。

```go
type S2 struct {
	F1      int
	private int
}

func main() {
	v1 := S2{F1: 1, private: 1}
	v2 := S2{F1: 1, private: 1}
	v3 := S2{F1: 1, private: 2}

	// AllowUnexported を指定して比較すると unexported な部分が違ってもdiffが出る
	opt := cmp.AllowUnexported(v1)

	if diff := cmp.Diff(v1, v2, opt); diff != "" {
		fmt.Printf("v1 != v2\n%s\n", diff)
	} else {
		fmt.Println("v1 == v2")
	}

	if diff := cmp.Diff(v1, v3, opt); diff != "" {
		fmt.Printf("v1 != v3\n%s\n", diff)
	} else {
		fmt.Println("v1 == v3")
	}
}
```

実行してみます。


```
$ go run main.go
v1 == v2
v1 != v3
{main.S2}.private:
	-: 1
	+: 2
```

予想通り？
unexported なフィールドのdiffも出るのですね。


## unexported なフィールドと戦う (3) 無視してもらう

unexportedなフィールドは無視してくれたほうが嬉しいこともあります。
`cmpopts.IgnoreUnexported` を使うと無視してくれます。

```go
type S2 struct {
	F1      int
	private int
}

func main() {
	v1 := S2{F1: 1, private: 1}
	v2 := S2{F1: 2, private: 1}
	v3 := S2{F1: 1, private: 2}
	v4 := S2{F1: 2, private: 2}

	// cmpopts.IgnoreUnexported を指定すると unexported な部分でdiffがあっても無視してくれる
	opt := cmpopts.IgnoreUnexported(v1)

	if diff := cmp.Diff(v1, v2, opt); diff != "" {
		fmt.Printf("v1 != v2\n%s\n", diff)
	} else {
		fmt.Println("v1 == v2")
	}

	if diff := cmp.Diff(v1, v3, opt); diff != "" {
		fmt.Printf("v1 != v3\n%s\n", diff)
	} else {
		fmt.Println("v1 == v3")
	}

	if diff := cmp.Diff(v1, v4, opt); diff != "" {
		fmt.Printf("v1 != v4\n%s\n", diff)
	} else {
		fmt.Println("v1 == v4")
	}
}

```

はい。

```
$ go run main.go
v1 != v2
{main.S2}.F1:
	-: 1
	+: 2

v1 == v3
v1 != v4
{main.S2}.F1:
	-: 1
	+: 2
```

はい。
期待通り。

## いらないフィールドを無視する

ランダム値だったりパスワードのハッシュだったりが入るフィールドは無視してもらったほうがテストしやすいですよね。
そういうときは `cmpopts.IgnoreFields` で。

```go
type S3 struct {
	F1 int
	F2 int
}

func main() {
	v1 := S3{F1: 1, F2: 1}
	v2 := S3{F1: 2, F2: 1}
	v3 := S3{F1: 1, F2: 2}
	v4 := S3{F1: 2, F2: 2}

	// cmpopts.IgnoreFields を指定するとそのフィールドが違っても無視してくれる
	opt := cmpopts.IgnoreFields(v1, "F2")

	if diff := cmp.Diff(v1, v2, opt); diff != "" {
		fmt.Printf("v1 != v2\n%s\n", diff)
	} else {
		fmt.Println("v1 == v2")
	}

	if diff := cmp.Diff(v1, v3, opt); diff != "" {
		fmt.Printf("v1 != v3\n%s\n", diff)
	} else {
		fmt.Println("v1 == v3")
	}

	if diff := cmp.Diff(v1, v4, opt); diff != "" {
		fmt.Printf("v1 != v4\n%s\n", diff)
	} else {
		fmt.Println("v1 == v4")
	}
}
```

実行しましょう。

```
$ go run main.go
v1 != v2
{main.S3}.F1:
	-: 1
	+: 2

v1 == v3
v1 != v4
{main.S3}.F1:
	-: 1
	+: 2
```
最高ですね。


## ということで

go-cmp を単なるdiffがわかりやすく見えるよ、ではなくて Option を活用すると便利に使えるよ！ というお話でした。
その他にもいろいろ Option があったりするので、使っていきましょう。

- https://godoc.org/github.com/google/go-cmp/cmp#Option
- https://godoc.org/github.com/google/go-cmp/cmp/cmpopts