LoginSignup
0
0

More than 5 years have passed since last update.

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

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

必要なもの

サンプルアプリ

この記事の中で使うサンプルアプリは👇にあります。
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に動作するコードを置いています。

ではまずはConsumerから。

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の設定ではまずはここに記載のものを押さえておけばよさそうです。
ConsumerProviderはそのままConsumerとProviderの名前です。何を指定してもOKなのでいい感じに命名しましょう :ok_hand:
LogDirPactDirもそれぞれログファイルとPactファイルの出力先となディレクトリのことです。
LogLevelはPactの検証中に出力されるログのレベル指定で"DEBUG", "INFO", "WARN", "ERROR"が指定できます。
内部でhashicorp/logutilsを使ってるようです。

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

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:

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0