GoでPactする

GoでどんなかんじでPactを使えるか書いていくよー :runner: 

:information_source: Pactについて、情報量はまだ少ない印象ですがその分公式は丁寧に説明してくれてる気がするので、そちらを一読することをオススメします。

# 必要なもの

* [pact-ruby-standalone](https://github.com/pact-foundation/pact-ruby-standalone) ... PactのCLIツール
* [pact-go](https://github.com/pact-foundation/pact-go) ... GoでPactを利用するために必要なSDK
* [pact_broker-docker](https://github.com/DiUS/pact_broker-docker) ... ConsumerとProviderとの間でPactを共有するためのツール

# サンプルアプリ

この記事の中で使うサンプルアプリは👇にあります。
[nihei9/dive-into-pact-go](https://github.com/nihei9/dive-into-pact-go)

サンプルアプリはレシピのサーバとレシピを見るためのCLIツールで、構造はこんなかんじです。
ビルド方法は👆のリポジトリをご覧ください。

```
dive-into-pact-go
├─ provider ............... レシピサーバ
│　　　　├─　cmd
│　　　　│　　　└─　recipes
│　　　　│　　　　　　　　└─　main.go
│　　　　└─ handler
│　　　　　　　　　　├─　handler.go
│　　　　　　　　　　└─　handler_test.go ... Providerの検証を実装したUT
└─ consumer ............... CLIツール
 　　　　├─　cmd
 　　　　│　　　└─　recipes
 　　　　│　　　　　　　　└─　main.go
 　　　　└─ client
 　　　　　　　　　　├─　client.go
 　　　　　　　　　　└─　client_test.go .... Consumerの検証を実装したUT
```

# 基本的な検証

ConsumerとProviderの検証だけならPact Brokerがなくてもできるようなので、まずはPact BrokerなしでPactをいじってみます。
GoからPactを利用するときはUTとして実装します。

:warning: 解説に使用しているコードは完全なものではありません。前述の通り[nihei9/dive-into-pact-go](https://github.com/nihei9/dive-into-pact-go)に動作するコードを置いています。

ではまずはConsumerから。

```go
var pact dsl.Pact

func TestMain(m *testing.M) {
	// Pactの設定
	pact = dsl.Pact{
		Consumer:          "consumer name",
		Provider:          "provider name",
		LogDir:            "path/to/log/dir",
		PactDir:           "path/to/pact/dir",
		LogLevel:          "DEBUG",
	}

	// テスト実行
	exitCode := m.Run()

	// Pactの書き出しと後処理
	pact.WritePact()
	pact.Teardown()

	os.Exit(exitCode)
}

func TestConsumer(t *testing.T) {
    // ConsumerのUT
}
```

大枠としては👆のような形がテンプレとして利用できそうです。（公式の実装例を真似てます）
`TestMain()`が入り口になっていて、その中で
1. Pactの設定
2. テスト実行
3. Pactの書き出し
という順で処理していきます。

ちなみに、`pact`をトップレベルで定義している理由は、`pact`に対してinteractionを定義したりConsumerの検証を実行したりする都合上、UT全体で共有する必要があるためです。

Pactの設定ではまずはここに記載のものを押さえておけばよさそうです。
`Consumer`と`Provider`はそのままConsumerとProviderの名前です。何を指定してもOKなのでいい感じに命名しましょう :ok_hand: 
`LogDir`と`PactDir`もそれぞれログファイルとPactファイルの出力先となディレクトリのことです。
`LogLevel`はPactの検証中に出力されるログのレベル指定で`"DEBUG", "INFO", "WARN", "ERROR"`が指定できます。
内部で[hashicorp/logutils](https://github.com/hashicorp/logutils)を使ってるようです。

`TestMain()`以外の`Test*()`にはPactでのConsumerの検証を実装していきます。

```go
func TestConsumer(t *testing.T) {
	// UTロジックを含む関数の定義
	var testSushiExists = func() error {
		c := New("localhost", pact.Server.Port)
		_, err := c.GetRecipe("12345678")
		if err != nil {
			return err
		}
		return nil
	}

	// interactionの定義
	pact.
		AddInteraction().
		// provider stateの設定
		Given("Recipe exists").
		// interactionの説明
		UponReceiving("A request to get a recipe").
		// Consumerからのリクエスト
		WithRequest(dsl.Request{
			Method: http.MethodGet,
			Path:   dsl.Term("/v1/recipes/12345678", "/v1/recipes/[0-9a-z]+"),
		}).
		// 期待するProviderからのレスポンス
		WillRespondWith(dsl.Response{
			Status: http.StatusOK,
			Body: dsl.Like(map[string]interface{}{
				"id":   dsl.Like("12345678"),
				"name": dsl.Like("Sushi"),
				"ingredients": dsl.EachLike(map[string]interface{}{
					"name": dsl.Like("rice"),
				}, 1),
			}),
			Headers: dsl.MapMatcher{
				"Content-Type": dsl.Term("application/json", `application\/json`),
			},
		})

	// 検証
	err := pact.Verify(testSushiExists)
	if err != nil {
		t.Fatalf("Error on Verify: %v", err)
	}
}
```

`Given()`はprovider stateを設定します。
`WithRequest()`と`WillRespondWith()`はそれぞれConsumerからのリクエストとProviderからのレスポンスの期待値を表します。
レスポンスの期待値はでは期待する値を固定値で指定することもできますが、多くの場合はそのドメインを指定することになりそうです。
その場合は`Like()`のようなメソッドを使用して正規表現で期待値のドメインを表現できます。

続く……
検証の実行はこの後書くよ :runner: