Kekeの日記

エンジニア、読書なんでも

Dreddを使ってHTTP APIサーバーとKubernetes Serviceのテストをする

f:id:bobchan1915:20181022185259p:plain

本記事

今回はAPIテストツールであるDreddを使ってHTTP APIサーバーのテストおよびKubernetes Serviceのテストをしようと思います。

テスト対象

今回はStripeSDKを使って構築した決済APIをテストしていきます。

目次

Dreddでテストをする

実行する

まず、以下のコマンドのようにテスト環境のサーバーを起動します。

env GO_ENV=test go run app/payment/payment.go

そしてテストします。

 dredd doc/v1/api.apib http://localhost:8880

すると

info: Beginning Dredd testing...
fail: POST (200) /v1/payment?stripeToken=tok_visa&customerID=cus_Dn1g1NTijBHzzl&userID=3&amount=4000&description=%22This%20is%20desc%22&email=hogehoge%40aho.com duration: 166ms
info: Displaying failed tests...
fail: POST (200) /v1/payment?stripeToken=tok_visa&customerID=cus_Dn1g1NTijBHzzl&userID=3&amount=4000&description=%22This%20is%20desc%22&email=hogehoge%40aho.com duration: 166ms
fail: body: At '/message' Missing required property: message
statusCode: Status code is '400' instead of '200'

request:
method: POST
uri: /v1/payment?stripeToken=tok_visa&customerID=cus_Dn1g1NTijBHzzl&userID=3&amount=4000&description=%22This%20is%20desc%22&email=hogehoge%40aho.com
headers:
    User-Agent: Dredd/5.2.0 (Darwin 18.0.0; x64)

body:



expected:
headers:
    Content-Type: application/json; charset=UTF-8

body:
{
  "error": "",
  "message": {
    "customerID": "cus_Dn1g1NTijBHzzl",
    "amount": 4000,
    "currency": "JPY",
    "description": "This is desc",
    "chargeID": "ch_hnc9Eaieeoau3442Nte"
  }
}
statusCode: 200


actual:
statusCode: 400
headers:
    content-type: application/json; charset=UTF-8
    date: Tue, 23 Oct 2018 03:17:36 GMT
    content-length: 424
    connection: close

bodyEncoding: utf-8
body:
{
  "error": {
    "message": "{\"status\":401,\"message\":\"You did not provide an API key, though you did set your Authorization header to \\\"Bearer\\\". Using Bearer auth, your Authorization header should look something like 'Authorization: Bearer YOUR_SECRET_KEY'. See https://stripe.com/docs/api#authentication for details, or we can help at https://support.stripe.com/.\",\"type\":\"invalid_request_error\"}",
    "statusCode": 400
  }
}



complete: 0 passing, 1 failing, 0 errors, 0 skipped, 1 total
complete: Tests took 171ms

のようにエラーがでます。

決済をするのにTokenIDというものが必要です。それをHookという機能を使って作成します。

Hockを実装する

以下のようにHookをGoで実装しました。

package main

import (
    "github.com/snikch/goodman/hooks"
    trans "github.com/snikch/goodman/transaction"
)

func main() {
    h := hooks.NewHooks()
    server := hooks.NewServer(hooks.NewHooksRunner(h))
     h.BeforeAll(func(t []*trans.Transaction) {
      err := godotenv.Load("../.env.test")
      if err != nil {
            os.Exit(1)
    }
     })
    h.Before("Payment > Payment > 決済を行う\u3000", func(t *trans.Transaction) {
              // token生成
    })
    server.Serve()
    defer server.Listener.Close()
}

これはBeforeAllというすべてのテストの前に実行されるHookで環境変数をロードしています。

そしてBeforeという特定のテストスイートで実行されるHookでトークンを生成します。

また、このTokenをクエリパラメータとしてセットしないといけないわけですが、t.FullPathでパスが取得できるので正規表現を使って置換しようと思います。

Token生成のロジックは今回の内容と関係ないので省きますが、以下のようにクエリパラメーターを置換することができます。

h.Before("Payment > Payment > 決済を行う\u3000", func(t *trans.Transaction) {
        token, _ := createToken()
        t.FullPath = strings.Replace(t.FullPath, "tok_visa", token.ID, 1)
    })

また、自動化のためにMakefileを書きました。

DREDD_COMMAND=dredd
GO_COMMAND=go
HOOK_FILE=./dredd.go
OUTPUT_FILE=dredd
APIB_FILE=./v1/api.apib 
PORT=8880

.PHONY: test
test:
    $(GO_COMMAND) build -o $(OUTPUT_FILE) $(HOOK_FILE) 
    $(DREDD_COMMAND) $(APIB_FILE) http://localhost:$(PORT) --language=go --hookfiles=$(OUTPUT_FILE)

これでテストをします。

info: Beginning Dredd testing...
info: Found Hookfiles: 0=/Users/keisukeyamashita/src/go/src/github.com/KeisukeYamashita/payment/doc/dredd
info: Spawning 'go' hooks handler process.
info: Hooks handler stdout: Sending to channel

Completed

info: Hooks handler stderr: 2018/10/23 13:21:59 Starting hooks server

info: Hooks handler stdout: Starting

info: Hooks handler stdout: Accepting connection

info: Successfully connected to hooks handler. Waiting 0.1s to start testing.
info: Hooks handler stderr: 2018/10/23 13:22:00 Requesting POST api.stripe.com/v1/tokens

pass: POST (200) /v1/payment?stripeToken=tok_visa&customerID=cus_Dn1g1NTijBHzzl&userID=3&amount=4000&description=%22This%20is%20desc%22&email=hogehoge%40aho.com duration: 1861ms
info: Hooks handler stderr: 2018/10/23 13:22:02 Shutting down hooks servers

complete: 1 passing, 0 failing, 0 errors, 0 skipped, 1 total
complete: Tests took 3384ms

のようになりました。

Dreddを使ってみた感じ

  • APIBlueprintのLintとしてはよい。
  • Dreddの機能不足なオプショナルの排除などはHookを書かないといけない
    • 「XXXの限りこのパラメーターは必要」などはAPI Blueprint上できない
    • クエリストリングを変えるにはFullPathから正規表現を使って取るしかない、、、formParamsなどとMapであればうれしいのに、、、
  • エラーを含む複数のレスポンスに対してテストすることはできない

例えば、決済でも以下のような種類があります。

+ Response 200 (application/json; charset=UTF-8)
    + Body

                {
                    "error": "",
                    "message": {
                        "customerID": "cus_Dn1g1NTijBHzzl",
                        "amount": 4000,
                        "currency": "JPY",
                        "description": "This is desc",
                        "chargeID": "ch_hnc9Eaieeoau3442Nte"
                    }
                }

+ Response 400 (application/json; charset=UTF-8)
    リクエストフォーマットに関するエラーです。

    + Body

            {
                "error": {
                    "statusCode": 400,
                    "message": "Message about the error",
                }
            }


+ Response 500 (application/json; charset=UTF-8)
    APIのデータベースに関するエラーです。

    + Body

                {
                    "error": {
                        "statusCode": 500,
                        "message": "Message about the error",
                    }
                }

それは以下のように修正すればテストをすることができるらしいです。

github.com

+ Request (application/json)

        {"color": "yellow"}


+ Response 200 (application/json)

        {"color": "yellow", "id": 1}


+ Request Edge Case (application/json)

        {"weight": 1}

+ Response 400 (application/vnd.error+json)

        {"message": "Validation failed"}

しかし、API Blueprintがエラーのためのリクエストを表示することになってしまい、Aglioなどのレンダリング結果が見辛くなってしまうので控える方がいいかもしれません。なので成功するかしないかというテストにはいいでしょう。 また、その場合は注意点があり、200を返すResponceを最初に書かないといけないことに注意してください。

  • GUIが提供されている

--reporter=apiaryのオプションをつけると、以下のようにGUIで確認できるので、より理解しやすくなります。

f:id:bobchan1915:20181023143922p:plain

Kubernetes Serviceをテストする

テストをしたい対象が

  • Service: type:ClusterIP
  • Deployment: 今回のサーバー

です。

Jobを使う

Kubernetes Jobを使えば、一回きりのテストができます。

例えばdredd-paymentイメージを作って以下のようにするといいでしょう。

apiVersion: batch/v1
kind: Job
metadata:
  name: dredd-job
spec:
  completions: 1
  parallelism: 1
  backoffLimit: 3
  template:
    spec:
      containers:
      - name: k8s-job
        image: asia.gcr.io/hogehoge-project/dredd-payment:0.1
      restartPolicy: Never

このようにするとServiceのテストをすることができます。

kubectl create -f dredd-job.yaml

後はこのJobが完了するのを待つだけです。

Jobが完了したかは私が知る限りはWebhookなどはないと思うので、SpinnakerなどでCDをしてWebhookを指定するのがいいかもしれません。