RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

【テスト自動化】APIテストの自動化ツールを調査してみた

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

テスト実行

% 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.yamloutput.verbosetrueにすることでリクエストとレスポンスが標準出力される
# 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 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 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がいいのではと思っています。


エンジニア中途採用サイト
ラクスでは、エンジニア・デザイナーの中途採用を積極的に行っております!
ご興味ありましたら是非ご確認をお願いします。
20210916153018
https://career-recruit.rakus.co.jp/career_engineer/

カジュアル面談お申込みフォーム
どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。
以下フォームよりお申込みください。
rakus.hubspotpagebuilder.com

ラクスDevelopers登録フォーム
20220701175429
https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/

イベント情報
会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください!

◆TECH PLAY
techplay.jp

◆connpass
rakus.connpass.com

Copyright © RAKUS Co., Ltd. All rights reserved.