勤怠サービスの開発チームに所属している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