kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Step CI で API のテストを自動化しよう

API のテストを自動化するなら Step CI が便利❗️

ちょうど導入するかどうか検証をしていて,とても良かったので紹介も兼ねてまとめたいと思う📝

Step CI は API として REST 以外に GraphQL や gRPC などもサポートしていて,実行方法としては CLI や GitHub Actions 以外に Jest など多くのサービスとの連携もサポートしている👏 また OpenAPI 仕様書からテストワークフローを自動生成する機能もある \( 'ω')/

stepci.com

docs.stepci.com

セットアップ

Step CI CLI は Homebrew や npm を使って簡単にセットアップできる.Step CI はデフォルトだと使用状況データを収集するため,気になるなら環境変数 STEPCI_DISABLE_ANALYTICS を設定しておくと良いと思う👌

$ brew install stepci

$ export STEPCI_DISABLE_ANALYTICS=true

$ stepci --version
2.6.7

docs.stepci.com

Step CI を試す: 基本操作

今回はサンプルとして犬の画像をランダムに返してくれる RandomDog API を使って Step CI を試す🐶

github.com

Step CI では YAML でテストワークフローを定義する.書きやすく読みやすく良いと思う.以下のワークフローでは API の実行結果に対して3種類のチェックを実行している❗️

  • Status Code 200 が返ってくること ✔
  • Response Header application/json; charset=utf-8 が返ってくること ✔
  • Response の JSON 構造が正しいこと ✔
version: 1.1
name: RandomDog Check
env:
  host: random.dog
tests:
  example:
    steps:
      - name: GET request
        http:
          url: https://${{env.host}}/woof.json
          method: GET
          check:
            status: 200
            headers:
              Content-Type: application/json; charset=utf-8
            schema:
              type: object
              properties:
                url:
                  type: string
                fileSizeBytes:
                  type: integer
              required:
                - url
                - fileSizeBytes

stepci run コマンドを実行すると PASS と返ってくる👌

$ stepci run workflow1.yml
 PASS  example ⏲ 1.257s ⬆ 0 bytes ⬇ 0 bytes

Tests: 0 failed, 1 passed, 1 total
Steps: 0 failed, 0 skipped, 1 passed, 1 total
Time:  1.282s, estimated 1s
CO2:   0.00003g

Workflow passed after 1.282s
Give us your feedback on https://step.ci/feedback

意図的に Status Code 500 を期待値にして実行すると FAIL と返ってくる👌

テストが落ちたときの情報量が多いのはデバッグしやすくて助かる.

$ stepci run workflow1.yml
 FAIL  example ⏲ 1.046s ⬆ 0 bytes ⬇ 0 bytes

Summary

  ✕ GET request failed after 1.039s

● example › GET request

Request  HTTP

GET https://random.dog/woof.json HTTP/1.1

Response

HTTP/1.1 200 OK
server: nginx/1.4.6 (Ubuntu)
date: Sun 16 Jul 2023 13:17:01 GMT
content-type: application/json; charset=utf-8
content-length: 91
connection: close
x-powered-by: Express
access-control-allow-origin: *
access-control-allow-headers: Origin X-Requested-With Content-Type Accept
access-control-allow-methods: GET
etag: W/"5b-JQhIhZUD7lbzPPh8fSXWfgKfDSs"
strict-transport-security: max-age=15768000

{"fileSizeBytes":75171,"url":"https://random.dog/b1a59f58-452a-4d97-82bb-a70d75c33090.JPG"}

Checks

Headers

  ✔ Content-Type: application/json; charset=utf-8

JSON Schema

  ✔ [object Object]

Status

  ✕ 200 (expected 500)

Tests: 1 failed, 0 passed, 1 total
Steps: 1 failed, 0 skipped, 0 passed, 1 total
Time:  1.403s, estimated 1s
CO2:   0.00003g

Workflow failed after 1.403s
Give us your feedback on https://step.ci/feedback

Step CI を試す: パフォーマンスと SSL

Step CI では,API に対して「パフォーマンス」「SSL」のチェックも実行できる.

API は継続的に動いているけど "あるタイミングからレスポンスが遅くなってしまっている" という場合や "SSL 証明書の更新を忘れている" という場合など,API レスポンス以外の観点までテストできて便利❗️

version: 1.1
name: RandomDog Check
env:
  host: random.dog
tests:
  example:
    steps:
      - name: GET request
        http:
          url: https://${{env.host}}/woof.json
          method: GET
          check:
            performance:
              firstByte:
                - lte: 200
              total:
                - lte: 1000
            ssl:
              valid: true
              signed: true
              daysUntilExpiration:
                - gte: 30

同じく stepci run コマンドを実行すると PASS と返ってくる👌

$ stepci run workflow2.yml
 PASS  example ⏲ 0.989s ⬆ 0 bytes ⬇ 0 bytes

Tests: 0 failed, 1 passed, 1 total
Steps: 0 failed, 0 skipped, 1 passed, 1 total
Time:  1.014s, estimated 1s
CO2:   0.00003g

Workflow passed after 1.014s
Give us your feedback on https://step.ci/feedback

RandomDog API の SSL 証明書には Let's Encrypt が使われていて,(動作確認をしている日だと)有効期限 60日以上 を期待値にして実行すると FAIL と返ってくる👌 ちなみに daysUntilExpiration でエラーになると expected [object Object] と表示されるのはちょっと微妙な気がする💨

$ stepci run workflow2.yml
 FAIL  example ⏲ 0.969s ⬆ 0 bytes ⬇ 0 bytes

Summary

  ✕ GET request failed after 0.969s

● example › GET request

Request  HTTP

GET https://random.dog/woof.json HTTP/1.1

Response

HTTP/1.1 200 OK
server: nginx/1.4.6 (Ubuntu)
date: Sun 16 Jul 2023 13:32:09 GMT
content-type: application/json; charset=utf-8
content-length: 93
connection: close
x-powered-by: Express
access-control-allow-origin: *
access-control-allow-headers: Origin X-Requested-With Content-Type Accept
access-control-allow-methods: GET
etag: W/"5d-jdBzoBmENWVOT0rkNffI3V7+CSY"
strict-transport-security: max-age=15768000

{"fileSizeBytes":1439974,"url":"https://random.dog/8e05486e-bd82-4656-b77d-f7ae95281969.jpg"}

Checks

Performance

  ✔ firstByte: 194
  ✔ total: 936

Certificate

  ✔ valid: true
  ✔ signed: true
  ✕ daysUntilExpiration: 34 (expected [object Object])

Tests: 1 failed, 0 passed, 1 total
Steps: 1 failed, 0 skipped, 0 passed, 1 total
Time:  1.331s, estimated 1s
CO2:   0.00003g

Workflow failed after 1.331s
Give us your feedback on https://step.ci/feedback

Step CI を試す: テストデータとフェイクデータ

次は Step CI の「テストデータ」「フェイクデータ」を試すため,Step CI のドキュメントにも載っている JSONPlaceholder API を使って POST リクエストを実行する❗️

github.com

テストデータ

Step CI では CSV のテストデータを用意してテストワークフローのパラメータとして使える.例えば今回は testdata.csv として以下の CSV を用意した.

name,blog
kakakakakku,https://kakakakakku.hatenablog.com/

すると YAML で ${{ testdata.name }}${{ testdata.blog }} のように定義できる.以下のワークフローでは POST リクエストのパラメータとしてテストデータから値を取得している👌 さらに check.jsonpath を使ってレスポンスの値をテストしている.

version: 1.1
name: JSONPlaceholder Check
env:
  host: jsonplaceholder.typicode.com
  resource: posts
tests:
  example:
    testdata:
      file: testdata.csv
    steps:
      - name: POST request
        http:
          url: https://${{env.host}}/${{env.resource}}
          method: POST
          headers:
            Content-Type: application/json
          json:
            name: ${{ testdata.name }}
            blog: ${{ testdata.blog }}
          check:
            status: /^20/
            jsonpath:
              $.name:
                - eq: kakakakakku
                - isString: true
              $.blog:
                - eq: https://kakakakakku.hatenablog.com/
                - isString: true

実行すると PASS と返ってくる👌

$ stepci run workflow3.yml
 PASS  example ⏲ 0.507s ⬆ 0 bytes ⬇ 0 bytes

Tests: 0 failed, 1 passed, 1 total
Steps: 0 failed, 0 skipped, 1 passed, 1 total
Time:  0.532s, estimated 1s
CO2:   0.00003g

Workflow passed after 0.532s
Give us your feedback on https://step.ci/feedback

フェイクデータ

Step CI には Faker が組み込まれているため,テストデータをランダムに生成することもできる.以下のワークフローでは POST リクエストのパラメータで ${{ internet.userName | fake }} と書くことで Faker の internet.userName からフェイクデータを取得している👌

version: 1.1
name: JSONPlaceholder Check
env:
  host: jsonplaceholder.typicode.com
  resource: posts
tests:
  example:
    testdata:
      file: testdata.csv
    steps:
      - name: POST request
        http:
          url: https://${{env.host}}/${{env.resource}}
          method: POST
          headers:
            Content-Type: application/json
          json:
            name: ${{ internet.userName | fake }}
          check:
            status: /^20/
            jsonpath:
              $.name:
                - eq: kakakakakku
                - isString: true

レスポンスとして name: kakakakakku を期待するテストワークフローになっているため,Faker によってランダムに生成されたユーザー名だとテストは落ちてしまう.以下の例だと Verdie28 というユーザー名になっていた.

$  stepci run workflow4.yml
 FAIL  example ⏲ 0.503s ⬆ 0 bytes ⬇ 0 bytes

Summary

  ✕ POST request failed after 0.499s

● example › POST request

Request  HTTP

POST https://jsonplaceholder.typicode.com/posts HTTP/1.1
Content-Type: application/json

{"name":"Verdie28"}
Response

HTTP/1.1 201 Created
date: Sun 16 Jul 2023 13:47:50 GMT
content-type: application/json; charset=utf-8
content-length: 37
connection: close
x-powered-by: Express
x-ratelimit-limit: 1000
x-ratelimit-remaining: 997
x-ratelimit-reset: 1689515291
vary: Origin X-HTTP-Method-Override Accept-Encoding
access-control-allow-credentials: true
cache-control: no-cache
pragma: no-cache
expires: -1
access-control-expose-headers: Location
location: http://jsonplaceholder.typicode.com/posts/101
x-content-type-options: nosniff
etag: W/"25-S03dZwwrthzEz7DRUq+Sn1rFo2k"
via: 1.1 vegur
cf-cache-status: DYNAMIC
report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=TWyG5TtvcoxyKpipwg4b7cKP1jVYhTp39K4PX9SqCe7cdV02sk3DWjk%2BGq9HY25iw%2BY8NbBMhDO65BUa%2F4RiXXaFQvcOWGLA8AOsmpgNWLKAAMHQXQ9Nn8%2FZBxCeruQ3dokzVWjvUOtHruuvKJiSrx95hab5GIW7qXjD"}]"group":"cf-nel""max_age":604800}
nel: {"success_fraction":0"report_to":"cf-nel""max_age":604800}
server: cloudflare
cf-ray: 7e7ab3071ba680e4-NRT
alt-svc: h3=":443"; ma=86400

{
  "name": "Verdie28",
  "id": 101
}

Checks

JSONPath

  ✕ $.name: Verdie28 (expected [object Object],[object Object])

Status

  ✔ 201

Tests: 1 failed, 0 passed, 1 total
Steps: 1 failed, 0 skipped, 0 passed, 1 total
Time:  0.879s, estimated 1s
CO2:   0.00001g

Workflow failed after 0.879s
Give us your feedback on https://step.ci/feedback

GitHub Actions で Step CI を実行する

Step CI CLI で動作確認をしたら,今度は GitHub Actions で継続的に API のテストを実行したいと思う.Step CI は GitHub Actions をサポートしているため Step CI Action を使えば簡単に実行できる👏

github.com

以下のような GitHub Actions ワークフローを定義すれば OK❗️

name: step-ci

on:
  push:
    branches:
      - master
  pull_request:
    branches:
      - master

jobs:
  step-ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run Step CI
        uses: stepci/stepci@main
        with:
          workflow: workflow1.yml

GitHub Actions で Step CI を実行できた!

まとめ

API のテストを自動化するなら Step CI が便利❗️

\( 'ω')/ もっと使っていくぞ〜

ちなみに今回は試さなかったけど Step CI には他にも機能があるので箇条書きにしておく👀

  • OpenAPI 連携
  • 負荷テスト
  • Fuzz テスト(API に不正な値を与える)
  • CO2 テスト(二酸化炭素排出量の計算?)
  • など