
勤怠サービスの開発チームに所属しているkarabishです。
テストに関するある課題を解決するためにAPIテストの自動化ツールを調査しました。まだチーム内に展開していないのですが、調査結果のうちツールの選定に関する部分を備忘録として残しておこうと思います。
なぜAPIテストを自動化するのか
36協定の計算などの負荷が重たい処理はpub/subアーキテクチャを利用して非同期で処理していました。ただ、publish側とsubscribe側それぞれのユニットテストは存在していたのですが、全体に関するテストは自動化されていませんでした。そのため、pub/sub全体に関するテスト観点をAPIテストで自動化しようと目論んだのが発端となります。
ツールの選定方針
選定方針を挙げるとすればこの3つになるのかと思います。
- CIとの親和性が高いこと
- dockerで扱いやすいこと
- yamlなどのテキストに定義するだけでテストができること
なぜこの3点なのかというと、APIテスト自体はCI上で実行する予定のため「CIとの親和性」が重要で、CIはGitlab CI(executorはdockerを利用)のためdockerで実行できる必要があり、dockerで実行するとなるとコードを書かずにyamlなどで定義できればありがたい、という背景になります。
調査したツールたち
調査したツールたちは以下の5つです。調査はしませんでしたが候補としてあがったツールたちは参考までに調査しなかったツールたちにまとめてあります。
| ツール | URL | ライセンス | 実装する言語 | 対応しているプロトコル | dockerイメージ |
|---|---|---|---|---|---|
| Tavern | 公式サイト, Github | MIT license | yaml, python | http, MQTT | ? |
| scenarigo | Github | Apache-2.0 license | yaml, golang | http, gRPC | ? |
| runn | Github | MIT license | yaml, golang | http, gRPC, DB, Chrome DevTools Protocol, SSH/Local command | ghcr |
| karate | 公式サイト, Github | MIT license | Gherkin | http, GraphQL | ? |
| stepci | 公式サイト, Github | MPL-2.0 license | yaml | REST, GraphQL, gRPC, tRPC, SOAP | ghcr |
調査方法
- OpenAPIのpetstore.yamlを利用したモック環境を用意する
GET /pet/1リクエストに対して成功した場合(200 OKを期待値とする)を実施する
% docker run --name openapi -d -p 4010:4010 stoplight/prism:3 mock -d -h 0.0.0.0 https://raw.githubusercontent.com/openapitools/openapi-generator/master/modules/openapi-generator/src/test/resources/3_0/petstore.yaml 9b6c556320adfacd9b3a497af35df5774b64abd406f022478254e1cc7ec42600
# cURLで実行した場合のレスポンス
% curl -sS -H 'api_key: special-key' -H "Accept: application/json" http://localhost:8080/pet/1 | jq .
{
"name": "officia magna",
"photoUrls": [
"Duis ex incididunt",
"sit"
],
"id": 1824362991613808600,
"category": {
"id": -6773680898015056000,
"name": "zq8QbcVd.U6hh9W0Pl01Dpq"
},
"tags": [
{
"id": 623842466761203700,
"name": "dolor est occaecat ea adipisicing"
}
],
"status": "available"
}
調査結果
Tavern
テストシナリオ
% cat api-test.tavern.yaml
test_name: sample
stages:
- name: GET /pet/1
request:
url: http://ローカルのIPアドレス:8080/pet/1
method: GET
headers:
accept: application/json
api_key: special-key
response:
status_code: 200
テスト実行
- 公式のdockerイメージが見つからなかったため、chatworkさん提供のイメージを利用させていただいた
% docker run -it --rm -v ${PWD}:/tavern chatwork/tavern:1.7.0 /tavern/api-test.tavern.yaml
.
------------------------------------------------------------------------------
Ran 1 tests in 0.25s
OK
scenarigo
テストシナリオ
# cat scenarios/api-test.yaml
title: sample
steps:
- title: GET /pet/1
protocol: http
request:
method: GET
url: 'http://ローカルのIPアドレス:8080/pet/1'
header:
accept: application/json
api_key: special-key
expect:
code: 200
テスト実行
- golangのdockerイメージを実行し、scenarigoを
go installでインストールする
% docker run -it --rm -v ${PWD}:/tests golang:1.19.3-bullseye bash
# go install github.com/zoncoen/scenarigo/cmd/scenarigo@v0.12.8
- scenarigoの設定ファイルを作成する。
scenarigo config initでテンプレートは作成可能
# cat scenarigo.yaml schemaVersion: config/v1 scenarios: # Specify test scenario files and directories. # ↓このディレクトリにシナリオを配置する - scenarios pluginDirectory: ./gen # Specify the root directory of plugins. # plugins: # Specify configurations to build plugins. # plugin.so: # Map keys specify plugin output file path from the root directory of plugins. # src: ./path/to/plugin # Specify the source file, directory, or "go gettable" module path of the plugin. output: verbose: false # Enable verbose output. # colored: false # Enable colored output with ANSI color escape codes. It is enabled by default but disabled when a NO_COLOR environment variable is set (regardless of its value). # report: # json: # filename: ./report.json # Specify a filename for test report output in JSON. # junit: # filename: ./junit.xml # Specify a filename for test report output in JUnit XML format.
scenarigo runでテストを実行する
# cd /tests/ # scenarigo run ok scenarios/api-test.yaml 0.036s
# scenarigo run
=== RUN scenarios/api-test.yaml
=== RUN scenarios/api-test.yaml/sample
=== PAUSE scenarios/api-test.yaml/sample
=== CONT scenarios/api-test.yaml/sample
=== RUN scenarios/api-test.yaml/sample/GET_/pet/1
--- PASS: scenarios/api-test.yaml (0.03s)
--- PASS: scenarios/api-test.yaml/sample (0.03s)
--- PASS: scenarios/api-test.yaml/sample/GET_/pet/1 (0.03s)
[0] send request
request:
method: GET
url: http://ローカルのIPアドレス:8080/pet/1
header:
Accept:
- application/json
Api_key:
- special-key
User-Agent:
- scenarigo/v0.12.8
response:
header:
Access-Control-Allow-Credentials:
- "true"
Access-Control-Allow-Headers:
- "*"
Access-Control-Allow-Origin:
- "*"
Access-Control-Expose-Headers:
- "*"
Connection:
- keep-alive
Content-Length:
- "222"
Content-Type:
- application/json
Date:
- ****
body:
category:
id: "1575230569806000000"
name: 9O7c1pJOPktof
id: "8291010298486120000"
name: consequat nulla sint
photoUrls:
- consequ
status: pending
tags:
- id: "1005964459763441700"
name: dolor Lorem sunt
elapsed time: 0.027939 sec
PASS
ok scenarios/api-test.yaml 0.033s
runn
テストシナリオ
% cat api-test.yaml
desc: sample
runners:
req: http://ローカルのIPアドレス:8080
steps:
getPet:
req:
/pet/1:
get:
headers:
accept: "application/json"
api_key: "special-key"
test: steps.getPet.res.status == 200
テスト実行
- 用意されているdockerイメージを利用することで実行することができる
% docker run -it --rm --name runn -v $PWD:/books ghcr.io/k1low/runn:v0.52.3-slim run /books/api-test.yaml sample ... ok 1 scenario, 0 skipped, 0 failures
--debugオプションを指定することでリクエストとレスポンスの内容が標準出力される
% docker run -it --rm --name runn -v $PWD:/books ghcr.io/k1low/runn:v0.52.3-slim run --debug /books/api-test.yaml
Run 'req' on 'sample'.steps.getPet
-----START HTTP REQUEST-----
GET /pet/1 HTTP/1.1
Host: ローカルのIPアドレス:8080
Accept: application/json
Api_key: special-key
-----END HTTP REQUEST-----
-----START HTTP RESPONSE-----
HTTP/1.1 200 OK
Content-Length: 452
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: *
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: *
Connection: keep-alive
Content-Type: application/json
Date: ****
{"name":"adipisicing eiusmod Excepteur nostrud","photoUrls":["ipsum dolor"],"id":4648156526148719000,"category":{"id":-1107853279573229600,"name":"8PbcHZGjikgwWWr"},"tags":[{"id":-3453614735764951000,"name":"dolore"},{"id":-6507718611499348000,"name":"ex Excepteur laboris mollit occaecat"},{"id":2671625534111551500,"name":"mollit"},{"id":-9126589410744205000,"name":"id Ut Lorem aliqua"},{"id":2174855446376759300,"name":"sed enim"}],"status":"sold"}
-----END HTTP RESPONSE-----
Run 'test' on 'sample'.steps.getPet
sample ... ok
1 scenario, 0 skipped, 0 failures
karate
テストシナリオ
% cat api-test.feature
Feature: sample
Background:
* def host = 'localhost:8080'
* def httpHeaders = { accept: 'application/json', api_key: 'special-key' }
Scenario: GET /pet/{id}
Given url 'http://' + host + '/pet/1'
And configure headers = httpHeaders
When method get
Then status 200
テスト実行
- Githubからjarファイルをダウンロードしておく
java -jar ${ダウンロードしたjarファイル} {テストシナリオ}で実行することができる
% java -jar karate-1.3.0.jar api-test.feature
00:00:00.000 [main] INFO com.intuit.karate - Karate version: 1.3.0
00:00:00.000 [main] INFO com.intuit.karate.Suite - backed up existing 'target/karate-reports' dir to: target/karate-reports_1669956006172
00:00:00.000 [main] DEBUG com.intuit.karate - request:
1 > GET http://localhost:8080/pet/1
1 > accept: application/json
1 > api_key: special-key
1 > Host: localhost:8080
1 > Connection: Keep-Alive
1 > User-Agent: Apache-HttpClient/4.5.13 (Java/11.0.11)
1 > Accept-Encoding: gzip,deflate
00:00:00.000 [main] DEBUG com.intuit.karate - response time in milliseconds: 49
1 < 200
1 < Access-Control-Allow-Origin: *
1 < Access-Control-Allow-Headers: *
1 < Access-Control-Allow-Credentials: true
1 < Access-Control-Expose-Headers: *
1 < Content-type: application/json
1 < Content-Length: 315
1 < Date: ****
1 < Connection: keep-alive
{"name":"laboris Ut","photoUrls":["elit ven","magna sint fugiat in occaecat","sit velit irure proident"],"id":6533262285796651000,"category":{"id":-5086239707500454000,"name":"jDU6rd4mMXrFOzsdIBp"},"tags":[{"id":-4193826791138492400,"name":"Duis anim"},{"id":-7590066944733139000,"name":"elit Ut"}],"status":"sold"}
---------------------------------------------------------
feature: api-test.feature
scenarios: 1 | passed: 1 | failed: 0 | time: 0.3456
---------------------------------------------------------
00:00:00.000 [main] INFO com.intuit.karate.Suite - <<pass>> feature 1 of 1 (0 remaining) api-test.feature
Karate version: 1.3.0
======================================================
elapsed: 1.57 | threads: 1 | thread time: 0.35
features: 1 | skipped: 0 | efficiency: 0.22
scenarios: 1 | passed: 1 | failed: 0
======================================================
HTML report: (paste into browser to view) | Karate version: 1.3.0
file:///${PWD}/target/karate-reports/karate-summary.html
===================================================================
stepci
テストシナリオ
% cat api-test.yaml
version: "1.1"
name: sample
env:
host: ローカルのIPアドレス:8080
tests:
getPet:
steps:
- name: GET /pet/1
http:
url: http://${{env.host}}/pet/1
method: GET
headers:
accept: application/json
api_key: special-key
check:
# http statusが200であることをチェックする
status: 200
テスト実行
- 用意されているdockerイメージを利用することで実行することができる
- Privacyに記載されている通り利用状況の収集を無効化するため
STEPCI_DISABLE_ANALYTICSを指定している
% docker run -it --rm -e STEPCI_DISABLE_ANALYTICS=yes -v ${PWD}:/tests ghcr.io/stepci/stepci:2.5.6 tests/api-test.yaml
PASS getPet
Tests: 0 failed, 1 passed, 1 total
Steps: 0 failed, 0 skipped, 1 passed, 1 total
Time: 0.216s, estimated 0s
Workflow passed after 0.216s
Give us your feedback on https://step.ci/feedback
調査しなかったツールたち
候補としては上がったが調査しなかったツールたちです。
| ツール | URL | ライセンス |
|---|---|---|
| cURL | 公式サイト, Github | ? |
| HTTPie | 公式サイト, Github | BSD-3-Clause license |
| Postman + Newman | Postmanの公式サイト, NewmanのGithub | NewmanはApache License 2.0 |
| insomnia | 公式サイト | - |
| api fortress | 公式サイト | - |
| Assertible | 公式サイト | - |
| speedscale | 公式サイト | - |
| Datadog | 公式サイト | - |
| Frisby | 公式サイト | BSD 3-Clause |
| SuperTest | Github | MIT license |
| Chakram | 公式サイト, Github | MIT license |
| REST Assured | Github | Apache-2.0 license |
| Pact | 公式サイト, Github | pact-goはMIT license |
| Dredd | 公式サイト, Github | MIT license |
まとめ
APIテストの自動化ツールを調査してみました。運用していないのでどういう問題が発生するかわからないのですが、導入するのはすんなりいけそうなツールがそこそこあるなという印象です。また、冒頭に記載した通りまだチームメンバーに展開していないのでどれを採用するのかはわからないのですが、複雑なことをする予定はないので個人的にはシンプルで簡単にできそうなstepciがいいのではと思っています。
エンジニア中途採用サイト
ラクスでは、エンジニア・デザイナーの中途採用を積極的に行っております!
ご興味ありましたら是非ご確認をお願いします。

https://career-recruit.rakus.co.jp/career_engineer/
カジュアル面談お申込みフォーム
どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。
以下フォームよりお申込みください。
rakus.hubspotpagebuilder.com
ラクスDevelopers登録フォーム

https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/
イベント情報
会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください!
◆TECH PLAY
techplay.jp
◆connpass
rakus.connpass.com