API Blueprintで書いたAPI仕様書をDreddを使ってCircleCIでテストしてみた

今回、あるプロジェクトで API Blueprint を使って、API仕様書を書くことになったので、 API Blueprint について調べていたのだが、 Dredd というAPIのテストフレームワークが便利そうだったので、 CircleCI で簡単なテストするところまで触ってみた。

Dredd は、 apiary.io が提供しているAPIのテストフレームワークで、 API Blueprint だけではなく、 Swagger にも対応している。

試してみた

API作成

まずは Golang で非常に簡単な API を作成。
/message にアクセスすると、 Hello World!! が返るだけ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"fmt"
"net/http"
"os"
)

func main() {
http.HandleFunc("/message", index)

p := port()
fmt.Println("Listening on Port", p)
http.ListenAndServe(p, nil)
}

func port() string {
port := os.Getenv("PORT")
if len(port) == 0 {
port = "8080"
}
return ":" + port
}

func index(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "Hello World!!")
}

go build をして、サーバーを起動。

1
2
$ ./simple-go-server
Listening on Port :8080

別のコンソールから、 curl を実行してちゃんとレスポンスが返ってくることを確認する。

1
2
$ curl localhost:8080/message
Hello World!!

API Blueprint でドキュメント作成

API Blueprint 形式のドキュメント doc.apib を作成する。

1
FORMAT: 1A

# GET /message
+ Response 200 (text/plain; charset=utf-8)

        Hello World!!

Dredd のインストール

Node.js がインストール済みの環境で、下記コマンドを実行して Dredd をインストールする。

1
2
$ npm install -g dredd
$ dredd --version

Dredd 設定ファイル作成

dredd init を実行することで対話形式で設定ファイル dredd.yml が作成できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ dredd init
? Location of the API description document doc.apib
? Command to start API backend server e.g. (bundle exec rails server) ./simple-go-server
? URL of tested API endpoint localhost:8080
? Programming language of hooks go
? Do you want to use Apiary test inspector? No
? Dredd is best served with Continuous Integration. Create CircleCI config for Dredd? No

Configuration saved to dredd.yml

Install hooks handler and run Dredd test with:

$ go get github.com/snikch/goodman/cmd/goodman
$ dredd
  • APIのドキュメントのファイル名
    • doc.apib を入力
  • API サーバー起動コマンド
    • ./simple-go-server を入力
  • APIのエンドポイント
    • localhost:8080 を入力
  • Hook を利用する際の言語
    • go を下記のように出てくる選択肢より選択
1
? Programming language of hooks
  ruby
  python
  nodejs
  php
  perl
❯ go
  rust
  • Apiary test inspector を利用するか
    • n を入力
  • CircleCI の設定ファイルを作成するか
    • 現時点では、CircleCI 2.0 には対応しておらず、1.0 の設定ファイル circle.yml が作られるため、 n を選択

生成された dredd.yml の設定内容はこんな感じになっている。

1
dry-run: null
hookfiles: null
language: go
sandbox: false
server: ./simple-go-server
server-wait: 3
init: false
custom: {}
names: false
only: []
reporter: []
output: []
header: []
sorted: false
user: null
inline-errors: false
details: false
method: []
color: true
level: info
timestamp: false
silent: false
path: []
hooks-worker-timeout: 5000
hooks-worker-connect-timeout: 1500
hooks-worker-connect-retry: 500
hooks-worker-after-connect-wait: 100
hooks-worker-term-timeout: 5000
hooks-worker-term-retry: 500
hooks-worker-handler-host: 127.0.0.1
hooks-worker-handler-port: 61321
config: ./dredd.yml
blueprint: doc.apib
endpoint: 'localhost:8080'

Dredd 実行

対話の最後に指示が出ており、その指示に従ってコマンドを実行する。

1
$ go get github.com/snikch/goodman/cmd/goodman

を実行して、 github.com/snikch/goodman/cmd/goodman をインストールした後に、 dredd コマンドを実行。
すると、先ほど作成した設定ファイル ./dredd.yml を見つけて、その設定に従って、APIサーバーを起動。
エンドポイントに対してアクセスをして、ドキュメント通りの期待したレスポンスが返ってくるかのテストを行ってくれる。

1
2
3
4
5
6
7
8
9
10
$ dredd
info: Configuration './dredd.yml' found, ignoring other arguments.
info: Starting backend server process with command: ./simple-go-server
info: Waiting 3 seconds for backend server process to start
Listening on Port :8080
info: Beginning Dredd testing...
pass: GET (200) /message duration: 29ms
complete: 1 passing, 0 failing, 0 errors, 0 skipped, 1 total
complete: Tests took 34ms
info: Backend server process exited

正常にテストが通った。
ちなみに、 doc.apib を下記のように、 HelloGood Morning に修正して、

1
FORMAT: 1A

# GET /message
+ Response 200 (text/plain; charset=utf-8)

        Good Morning World!!

テストを再度実行してみると……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
$ dredd
info: Configuration './dredd.yml' found, ignoring other arguments.
info: Starting backend server process with command: ./simple-go-server
info: Waiting 3 seconds for backend server process to start
Listening on Port :8080
info: Beginning Dredd testing...
fail: GET (200) /message duration: 30ms
info: Displaying failed tests...
fail: GET (200) /message duration: 30ms
fail: body: Real and expected data does not match.

request:
method: GET
uri: /message
headers:
User-Agent: Dredd/4.9.2 (Darwin 17.3.0; x64)
Content-Length: 0

body:



expected:
headers:
Content-Type: text/plain; charset=utf-8

body:
Good Morning World!!

statusCode: 200


actual:
statusCode: 200
headers:
date: Tue, 16 Jan 2018 08:22:59 GMT
content-length: 14
content-type: text/plain; charset=utf-8
connection: close

body:
Hello World!!




complete: 0 passing, 1 failing, 0 errors, 0 skipped, 1 total
complete: Tests took 34ms
info: Backend server process exited

ちゃんと fail: body: Real and expected data does not match. と表示されていて、 body が一致していないっていう内容で Fail した。
╭( ・ㅂ・)و ̑̑ グッ !

Apiary test inspector

ちなみに、設定ファイル作成時に、 Apiary test inspector を利用する設定にすると、 Apiary API key を聞かれる。
何も入力せずにエンターキーを押すと、テスト結果が https://app.apiary.io アップロードされ、パブリックにアクセスできるようになり、WEB上で確認できる。
テスト実行した最後に、下記のようにURLが表示されて、

1
complete: See results in Apiary at: https://app.apiary.io/public/tests/run/eb7093e0-565e-45e4-82f1-de14fda0cf72

URLにアクセスしてみると、下記のような画面となり、テストの結果が確認できる。
テスト結果は、24時間保持される。

Hook について

あるエンドポイントにアクセスする前に、テスト用のダミーデータを作成するとか、前のテストで作られたデータを次のテスト実行前に削除したい場合などには、 Hook を利用することができる。
dredd init を実行した際に、選択肢として表示されたように、 Hook で使用できる言語は、

  • Go
  • JavaScript (Sandboxed)
  • Node.js
  • Perl
  • PHP
  • Python
  • Ruby

となっている。

【参考URL】
Hook Scripts — Languages

Hook で利用できる種類はドキュメントを参考にしていただきたいが、下記の種類を指定できる。

  • beforeAll
  • beforeEach
  • before
  • beforeEachValidation
  • beforeValidation
  • after
  • afterEach
  • afterAll

【参考URL】
Hook Scripts — Types of Hooks

Hook 作成

ドキュメントの例を参考に、ほぼそのままではあるが test/hooks.go として下記のような Hook を作成した。
/message へアクセスする前に、コンソール上にメッセージを表示するだけ……。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"

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

func main() {
h := hooks.NewHooks()
server := hooks.NewServer(hooks.NewHooksRunner(h))
h.Before("/message > GET", func(t *trans.Transaction) {
fmt.Println("-------------------")
fmt.Println("before modification")
fmt.Println("-------------------")
})
server.Serve()
defer server.Listener.Close()
}

作成した上で、 go build を行う。

1
$ go build -o hooks test/hooks.go

hooks ファイルができるので、 dredd.yml で、

1
hookfiles: null

となっていた箇所を下記のように変更する。

1
hookfiles: hooks

再度テストを実行してみると、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$ dredd
info: Configuration './dredd.yml' found, ignoring other arguments.
info: Starting backend server process with command: ./simple-go-server
info: Waiting 3 seconds for backend server process to start
Listening on Port :8080
info: Beginning Dredd testing...
info: Found Hookfiles: 0=/Users/eugenesasa/work/src/github.com/mmmsasaki/simple-go-server/hooks
info: Spawning 'go' hooks handler process.
info: Hooks handler stdout: Sending to channel

Completed

info: Hooks handler stderr: 2018/01/18 07:42:27 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 stdout: -------------------
before modification
-------------------

pass: GET (200) /message duration: 53ms
info: Hooks handler stderr: 2018/01/18 07:42:28 Shutting down hooks servers

complete: 1 passing, 0 failing, 0 errors, 0 skipped, 1 total
complete: Tests took 5653ms
info: Backend server process exited

/message へアクセスする前に、メッセージが表示されており、 hooks が実行されているのが見えるかと思う。

1
2
3
info: Hooks handler stdout: -------------------
before modification
-------------------

以下の Express.js のサンプルでは、データのフィクスチャを読み込んだりしているので、こちらも参考に。

【参考URL】
dredd-example/hooks.js

CircleCI

これを、 CircleCI でも実行することで、継続的に実装した機能と、ドキュメントの乖離がないかということがチェックできる。
.circleci/config.yml は下記のようにした。

1
version: 2
jobs:
  build:
    docker:
      - image: circleci/golang:latest
    working_directory: /go/src/github.com/mmmsasaki/simple-go-server
    steps:
      - checkout
      - run: curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
      - run: sudo apt-get install -y nodejs
      - run: go version
      - run: node --version
      - run: npm --version
      - restore_cache:
          name: restore node_modules cache
          key: dredd
      - run:
          name: npm install dredd
          command: npm install dredd
      - save_cache:
          name: save node_modules cache
          key: dredd
          paths:
              - node_modules
      - run: go build
      - run: go get github.com/snikch/goodman/cmd/goodman
      - run: go build -o hooks ./test/hooks.go
      - run: ./node_modules/dredd/bin/dredd

ちゃんとテストが通っていることが確認できた。

キャッシュが効いていれば、今回の簡単なテストで35秒ぐらいで終わった。

まとめ

API仕様書の管理の課題として、

ドキュメント起票者のミスや、変更仕様の反映漏れなどにより、ドキュメント仕様と実際のAPIが乖離してしまい、使えないドキュメント化してしまう

ということが往々にしてあることだと思うが、 API BlueprintDredd を使い、 CircleCI で、ドキュメントとAPIの間で乖離がないか、というチェックを継続的に行えば、この問題は解決できそうな気もしている。
また、実際にサーバーを起動して、アクセスしてレスポンスをテストするので、 テストコードを書く工数ももしかしたら削減できるかもしれないな、と少し期待している。

今回の各種ファイルは下記リポジトリに置いてある。
mmmsasaki/simple-go-server

参考

公式ページ
API Blueprint
Dredd — HTTP API Testing Framework

こんなに使える!今どきのAPIドキュメンテーションツール
API Blueprintで書いたWeb APIをdreddでテストする - Qiita
Apiドキュメンテーションツールを使いこなす【api blueprint編】
API Blueprint で API 仕様書を書いて、配布用の HTML を自動生成する方法 | GMOアドパートナーズグループ TECH BLOG byGMO
API BlueprintでWeb APIのドキュメントを生成する - Qiita

このエントリーをはてなブックマークに追加