Step CIを用いたAPI自動テストのすゝめ
はじめに
こんにちは、D2Cのエンジニアの吉田です。
本日は私たちのチームが普段 Web API 開発で実際に利用している Step CI を用いたAPI自動テストの構築例を紹介しようと思います。
Step CI の簡単な使い方や利点をまとめ、CI/CDパイプラインとの統合についてまとめます。
Step CI とは
Step CI はオープンソースのAPI自動テストツールです。
言語にとらわれず、YAML形式で簡単にAPIテストを記述することが可能です。
この記事ではRESTベースのAPIテストを紹介しますが、GraphQL
、gRPC
、tRPC
、SOAP
といった異なるタイプのAPIテストを1つのワークフローでテストすることができます。
他にもCI/CDに組み込むことも可能で、Github Action
やBitbucket Pipelines
と統合することで継続的に自動テストを実行することができます。
また、テスト中に指定したレスポンスの値を名前付き変数で保持することが可能なので、後続の別のAPIテストケースで使用することができます。そのため、登録から参照、更新、削除といった複数のAPIを組み合わせたシナリオ試験にも適しています。
機能が豊富で使いやすいツールですので、ぜひ公式ドキュメントを覗いてみてください!
Step CI を用いたシナリオ試験例
それでは、APIのシナリオ試験を想定し、実際にStep CIを使っていきましょう!
動作の検証環境は以下の通りです。
- Step CI: v2.8.0
API仕様-Swagger
今回テストしたいAPIの仕様は以下の通りとします。
ログイン、商品登録、そして登録した商品の照会が可能な簡単な仕様とします。
UI表示で見たい場合は以下コードをSwagger Editorに貼り付けてご確認ください。
swagger.yml
openapi: 3.0.1
info:
version: '1.0'
title: Test API
servers:
- url: 'http://localhost:8080'
description: Test server
paths:
/api/v1/users/login:
post:
tags:
- Users
summary: ユーザーログイン
operationId: post-users-login
requestBody:
content:
application/json:
schema:
type: object
properties:
email:
type: string
example: samples@example.com
password:
type: string
example: xxxxxxxxx
required:
- email
- password
responses:
'201':
headers:
Set-Cookie:
description: セッションID
schema:
type: string
# 試験用の返却値のため設定は最低限
example: testSessionId=xxxx;path="*"
description: Successfully logged in
content:
application/json:
schema:
type: object
properties:
user_id:
type: string
format: uuid
example: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
required:
- user_id
/api/v1/items:
post:
tags:
- Items
summary: 商品作成
operationId: post-items
parameters:
- name: testSessionId
in: cookie
required: true
schema:
type: string
security:
- cookieAuth: []
requestBody:
content:
application/json:
schema:
type: object
properties:
name:
type: string
example: チョコレート
price:
type: integer
example: 1000
required:
- name
- price
responses:
'201':
description: Created
content:
application/json:
schema:
type: object
properties:
item_id:
type: string
format: uuid
example: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
required:
- item_id
/api/v1/items/{item_id}:
get:
tags:
- Items
summary: 商品取得
operationId: get-item-by-id
parameters:
- name: testSessionId
in: cookie
required: true
schema:
type: string
- name: item_id
in: path
schema:
type: string
required: true
security:
- cookieAuth: []
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
item_id:
type: string
format: uuid
example: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
name:
type: string
example: チョコレート
price:
type: integer
example: 1000
required:
- item_id
- name
- price
components:
securitySchemes:
cookieAuth:
type: apiKey
in: cookie
name: testSessionId
上記APIの概要は次の通りです。
- ユーザーログインAPI
POST /api/v1/users/login
- メールアドレスとパスワードからログインする。
- ログインに成功すると Cookie
testSessionId
を取得する。
- 商品作成API
POST /api/v1/items
-
testSessionId
が必須。 - ログイン済みのユーザーが、商品を作成する。
-
- 商品取得API
GET /api/v1/items/{item_id}
-
testSessionId
が必須。 - ログイン済みのユーザーが、商品IDに一致する商品を取得する。
-
これらのAPIに対して ①ログイン -> ②商品作成 -> ③作成した商品情報の取得 という一連のシナリオ試験を、Step CIを使って作成したいと思います。
シナリオ試験の作成 ① - ログイン
商品操作するにはまずログインが必要となるので、ログインの試験を書いていきます。
なお、事前に公式のGetting startedを参考にstepci
をインストールしていることを前提とします。
workflow.yml
を用意して、ログインAPIの試験を記述します。
version: "1.1"
name: Test API
env:
host: localhost:8080
tests:
scenario_1:
name: Login and register items
steps:
- id: post-users-login
name: ログイン
http:
# test対象のAPI URL
url: http://${{env.host}}/api/v1/users/login
method: POST
headers:
Content-Type: application/json
accept: application/json
# test時のリクエスト
json:
email: samples@example.com
password: xxxxxxxxx
# 検証したい内容
check:
status: 201
headers:
Content-Type: application/json
schema:
type: object
properties:
user_id:
type: string
format: uuid
required:
- user_id
jsonpath:
$.user_id: a2805875-ccb6-34f5-c5bc-1d205363caa9
captures:
sessionId:
cookie: testSessionId
初見でも分かりやすい構成になっているかと思います。
試験したいAPIの情報を記述し、リクエストボディはjson:
で指定可能です。
そして、レスポンスでチェックしたい内容をcheck:
配下に記述していきます。ここでは以下内容をチェックしていますが、必要に応じてチェック項目を追加したり、削ったりしましょう。
- レスポンスのHTTPステータスコード
- Content-Type
- レスポンスボディのJSONスキーマ
- レスポンスボディの各フィールド値
また、ログイン成功時のCookieにセットされたセッションIDは、後続のAPI試験でも使用したいので、captures:
により、変数名sessionId
(任意の名称を指定可能)に保持します。
テストの実行
さて、一度stepci
を実行し、テストしてみましょう。
テスト対象となるAPIは、今回は簡略化のため用意したSwaggerからモックサーバーを立て、それに対して試験することにします。
そのため、最初に提示したSwaggerのexample
の値と、テスト期待値を実は既に合わせてあります。
モックサーバーは、実際に私たちのチームで活用しているprismを使おうと思います。
Dockerコマンド一つでswagger.yml
からモックサーバーを起動してくれます。
docker run --rm -it -p 8080:4010 -v $PWD:/mock stoplight/prism:4 mock -h 0.0.0.0 /mock/swagger.yml
やや本題からずれましたが、準備はできたのでstep ci
によるテストを実行しましょう。
実行は簡単で、stepci run
コマンドを実行するだけでAPIテストを走らせることができます。
$ stepci run ./workflow.yml
実行すると以下のように結果がコンソールに出力され、テストが成功したことがわかります!
モックサーバーからはログイン時の戻り値でuser_id=a2805875-ccb6-34f5-c5bc-1d205363caa9
を返却するように設定してあるので、試験期待値と一致し、テストをパスします。
PASS Login and register items ⏲ 0.97s ⬆ 0 bytes ⬇ 0 bytes
Tests: 0 failed, 1 passed, 1 total
Steps: 0 failed, 0 skipped, 1 passed, 1 total
Time: 0.983s, estimated 1s
CO2: 0.00002g
Workflow passed after 0.983s
Give us your feedback on https://step.ci/feedback
シナリオ試験の作成 ② - 商品の作成と取得
ログインに成功してCookieからセッションIDを取得できるところまで試験できたので、その認証情報を用いて商品を作成し、作成した商品の商品IDを用いて商品情報を取得できるところまでのシナリオ試験を完成させます。
# ----- 追加 -----
- id: post-items
name: 商品作成
http:
url: http://${{env.host}}/api/v1/items
method: POST
cookies:
testSessionId: ${{captures.sessionId}} # ※1
headers:
Content-Type: application/json
accept: application/json
json:
name: チョコレート
price: 1000
check:
status: 201
schema:
type: object
properties:
item_id:
type: string
format: uuid
required:
- item_id
captures:
itemId:
jsonpath: $.item_id # ※2
- id: get-item-by-id
name: 商品取得
http:
url: http://${{env.host}}/api/v1/items/${{captures.itemId}} # ※2
method: GET
cookies:
testSessionId: ${{captures.sessionId}}
check:
status: 200
schema:
type: object
properties:
item_id:
type: string
format: uuid
name:
type: string
price:
type: integer
required:
- item_id
- name
- price
jsonpath:
$.item_id: ${{captures.itemId}} # ※2
$.name: チョコレート
$.price: 1000
# ----- 追加 -----
ログインの時と記述の流れはほぼ同じですが、ここでのポイントとしては以下2点です。
- ※1: ログイン時にキャプチャしたセッションIDを
${{captures.sessionId}}
で繰り返し利用できる点 - ※2: 商品作成APIのレスポンスで取得した商品IDを、
captures.itemId
で保持し、商品取得APIのURLや、レスポンスのチェックに使用できる点
これにより、「①ログイン -> ②商品作成 -> ③作成した商品情報の取得」 のシナリオ試験を作成することができました。
直感的にテストを記述できるので、非常に使いやすいですね!
以下、workflow.yml
全文
workflow.yml
version: "1.1"
name: Test API
env:
host: localhost:8080
tests:
scenario_1:
name: Login and register items
steps:
- id: post-users-login
name: ログイン
http:
# test対象のAPI URL
url: http://${{env.host}}/api/v1/users/login
method: POST
headers:
Content-Type: application/json
accept: application/json
# test時のリクエスト
json:
email: samples@example.com
password: xxxxxxxxx
# 検証したい内容
check:
status: 201
headers:
Content-Type: application/json
schema:
type: object
properties:
user_id:
type: string
format: uuid
required:
- user_id
jsonpath:
$.user_id: a2805875-ccb6-34f5-c5bc-1d205363caa9
captures:
sessionId:
cookie: testSessionId
- id: post-items
name: 商品作成
http:
url: http://${{env.host}}/api/v1/items
method: POST
cookies:
testSessionId: ${{captures.sessionId}}
headers:
Content-Type: application/json
accept: application/json
json:
name: チョコレート
price: 1000
check:
status: 201
schema:
type: object
properties:
item_id:
type: string
format: uuid
required:
- item_id
captures:
itemId:
jsonpath: $.item_id
- id: get-item-by-id
name: 商品取得
http:
url: http://${{env.host}}/api/v1/items/${{captures.itemId}}
method: GET
cookies:
testSessionId: ${{captures.sessionId}}
check:
status: 200
schema:
type: object
properties:
item_id:
type: string
format: uuid
name:
type: string
price:
type: integer
required:
- item_id
- name
- price
jsonpath:
$.item_id: ${{captures.itemId}}
$.name: チョコレート
$.price: 1000
Step CI の利点
Step CIを実際に業務でも使用してますが、私が感じている大きな利点は以下の通りです。
- yaml形式で誰でも簡単にすぐにテストを書くことができるので、学習コストが低い
- APIのレスポンスの値を変数として保持できるので、API同士のシナリオ試験にも向いている
- CI/CDに組み込み可能
- 公式のDockerイメージも用意されているので、すぐに実行可能
上記の他にも色々と機能がありますが、私的に中でも良いと思った機能抜粋です。
- Matchers: レスポンスのCheckに使用することで、より細かなレスポンス値の評価が可能
-
OpenAPIからのWorkflow生成 :
stepci generate
コマンドを使えば、swaggerからworkflow.yml
を自動生成することが可能
機能が豊富なので、ぜひ公式ドキュメントをぜひ見てみてください!
実際の運用方法
Bitbucket Pipelines との統合
私たちのチームはBitbucketを利用しているので、Step CIをBitbucket Pipelinesに組み込み、自動テストを実行しています。
Bitbucket Pipelinesでは、bitbucket-pipelines.yml
を用意して、パイプラインで実行する内容を記述します。
以下に示すのは、go言語のAPIアプリケーションに対するStep CIの実行例です。
npm install
をステップ上で利用して直接stepci
コマンドを実行する方法もありますが、ここではDockerを利用する方式を取ってます。
bitbucket-pipelines.yml
image: golang:1.22
pipelines:
default:
- step:
name: "Unit Test and Build"
script:
- echo "Starting go test..."
- go get
- go test ./...
- echo "Starting go build..."
- go build -o ./main
artifacts:
- main
- step:
name: "API Test Step CI"
script:
- echo "Running API Server..."
- ./main &
- echo "Starting Step CI Test..."
- docker run --add-host host.docker.internal:host-gateway -v "$(pwd)"/tests:/tests ghcr.io/stepci/stepci /tests/workflow.yml -e HOST=host.docker.internal:8080
- echo "stepci test completed."
services:
- docker
あとはPushするだけで、パイプラインが動き、Step CIによる自動テストが実行されます。簡単ですね!
開発時は、先にStep CIのテストを記述して失敗するテストコードを書き、それをグリーンにしてリファクタリングを繰り返すテスト駆動開発で進めることもできます。
詳細な運用方法はチームによりますが、いずれにせよローカル開発や、Github Action、Bitbucket Pipelinesなどに簡単に取り入れることが可能です。
最後に
Step CIいかがだったでしょうか?
オープンソースで、学習コストおよび導入コストが低いにも関わらず、柔軟なテストケースを表現できる点が推しポイントです。
この記事内で紹介できてはいないですが、ここで挙げた機能以外にも使えそうな機能が豊富にありますので、ぜひ一度調べて使ってみてください!
ありがとうございました!
参考
株式会社D2C d2c.co.jp のテックブログです。 D2Cは、NTTドコモと電通などの共同出資により設立されたデジタルマーケティング企業です。 ドコモの膨大なデータを活用した最適化を行える広告配信システムの開発をしています。
Discussion