Dreddを使ってHTTP APIサーバーとKubernetes Serviceのテストをする
本記事
今回は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", } }
それは以下のように修正すればテストをすることができるらしいです。
+ 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で確認できるので、より理解しやすくなります。
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を指定するのがいいかもしれません。