🏃‍♂️

API シナリオテストツール runn で E2E テストを実装する

2024/06/24に公開

API シナリオテストツール runn とは

今回は、API シナリオテストツールである runn について紹介します😉✨

https://github.com/k1LoW/runn

runn の主な特徴

runn は Go 言語で実装されているツールで、主な特徴は以下です。

発音は「ラン エヌ」です。

  • シナリオベースのテストツールとして機能
  • Go 言語用のテストヘルパーパッケージとして機能
  • ワークフロー自動化ツールとして機能
  • 以下をサポート:
    • HTTP リクエスト
    • gRPC リクエスト
    • DB クエリ
    • Chrome DevTools Protocol
    • SSH/ローカルコマンド実行
  • HTTP リクエストテストに OpenAPI 文書に似た構文を使用
  • 単一のバイナリファイル = CI (継続的インテグレーション)に適している

引用: k1LoW/runn: runn is a package/tool for running operations following a scenario.

そして、ロゴがめっちゃカッコ良いです...!!!

runn のインストール

Homebrew でインストールすることができます。

$ brew install k1LoW/tap/runn

その他のインストール方法はオフィシャルのページを参照ください。

E2E テストを書いてみる

runn を使うことで、バックエンド単体のE2Eテストをコード量を少なく書くことができます!

以下の TODO を扱う API の E2E テストを実装してみました。

runbook 全体

今回は 1 つのシナリオにまとめて、以下の API エンドポイントに関するテストを書いてみました。

  • /v1/hc
  • /v1/todos
test.yml
desc: "Todo API test"
runners:
  req:
    endpoint: http://127.0.0.1:8080/v1
vars:
  title: "My first todo!!!"
  description: "This is a todo for test."
steps:
  hc:
    desc: "Health Check"
    req:
      /hc:
        get:
          body:
            application/json: null
    test: |
      // Status code must be 204.
      current.res.status == 204
  hcPostgres:
    desc: "Health Check Postgres"
    req:
      /hc/postgres:
        get:
          body:
            application/json: null
    test: |
      // Status code must be 204.
      current.res.status == 204
  postTodo:
    desc: "Create Todo"
    req:
      /todos:
        post:
          body:
            application/json:
              title: "{{ vars.title }}"
              description: "{{ vars.description }}"
    test: |
      // Status code must be 201.
      current.res.status == 201
    bind:
      todoId: current.res.body.id
  getTodo:
    desc: "Get Todo"
    req:
      /todos/{{ todoId }}:
        get:
          body:
            application/json: null
    test: |
      // Status code must be 200.
      current.res.status == 200
      && current.res.body.id == todoId
  findTodo:
    desc: "Find Todo"
    req:
      /todos:
        get:
          body:
            application/json: null
    test: |
      // Status code must be 200.
      current.res.status == 200
      && len(current.res.body.todos) > 0
  findNewTodo:
    desc: "Find New Todo"
    req:
      /todos?status=new:
        get:
          body:
            application/json: null
    test: |
      // Status code must be 200.
      current.res.status == 200
      && len(current.res.body.todos) > 0
  putTodo:
    desc: "Update Todo"
    req:
      /todos/{{ todoId }}:
        put:
          body:
            application/json:
              title: "{{ vars.title }}"
              description: "{{ vars.description }}"
              statusCode: "working"
    test: |
      // Status code must be 200.
      current.res.status == 200
      && current.res.body.status.code == "working"
  patchTodo:
    desc: "Change Todo Stats"
    req:
      /todos/{{ todoId }}:
        patch:
          body:
            application/json:
              statusCode: "done"
    test: |
      // Status code must be 200.
      current.res.status == 200
      && current.res.body.status.code == "done"
  deleteTodo:
    desc: "Delete Todo"
    req:
      /todos/{{ todoId }}:
        delete:
          body:
            application/json: null
    test: |
      // Status code must be 200.
      current.res.status == 200
      && current.res.body.id == todoId

上から細かく見ていきます。

説明とエンドポイントの定義

desc: "Todo API test"
runners:
  req:
    endpoint: http://127.0.0.1:8080/v1

desc シナリオについての説明を書くことができます。

また、endpoint で共通となる API のエンドポイントを定義できます。

変数を定義する

vars で変数を定義することができます。

vars:
  title: "My first todo!!!"
  description: "This is a todo for test."

map でテストを書く

list 形式で steps を書くこともできますが、
保守性を考えて map 形式で書き、各ステップに名前を付けていきます。

以下では、hc がステップ名です。

steps:
  hc:
    desc: "Health Check"
    req:
      /hc:
        get:
          body:
            application/json: null
    test: |
      // Status code must be 204.
      current.res.status == 204

endpoint を定義しているため、/hc と書くだけで http://127.0.0.1:8080/v1 にリクエストを飛ばすことができます。

test では、レスポンスの HTTP ステータスコードが 204 であるかをチェックしています。

変数の使用

vars で定義した変数は title: "{{ vars.title }}" のように使用します。

レスポンスの作成された TODO の ID を bindtodoId に格納しています。

そうすることで、/todos/{{ todoId }} で GET のエンドポイントを生成することができます。

また、レスポンスの current.res.body.id と ID が一致するかも検証しています。

  postTodo:
    desc: "Create Todo"
    req:
      /todos:
        post:
          body:
            application/json:
              title: "{{ vars.title }}"
              description: "{{ vars.description }}"
    test: |
      // Status code must be 201.
      current.res.status == 201
    bind:
      todoId: current.res.body.id
  getTodo:
    desc: "Get Todo"
    req:
      /todos/{{ todoId }}:
        get:
          body:
            application/json: null
    test: |
      // Status code must be 200.
      current.res.status == 200
      && current.res.body.id == todoId

シナリオテストを実行する

run コマンドで、シナリオテストを実行できます。

なお、詳細なログを出力するために --verbose オプションをつけています。

$ runn run runbooks/test.yml --verbose
=== Todo API test (runbooks/test.yml)
    --- Health Check (hc) ... ok
    --- Health Check Postgres (hcPostgres) ... ok
    --- Create Todo (postTodo) ... ok
    --- Get Todo (getTodo) ... ok
    --- Find Todo (findTodo) ... ok
    --- Find New Todo (findNewTodo) ... ok
    --- Update Todo (putTodo) ... ok
    --- Change Todo Stats (patchTodo) ... ok
    --- Delete Todo (deleteTodo) ... ok


1 scenario, 0 skipped, 0 failures

出力

なお、dump を使えば、結果を出力して確認することもできます。

YAML ファイルを作成中は適宜出力すると便利です!

    dump: current.res.body

まとめ

チュートリアルも充実していて、サクッと E2E テストを実装することができました。

YAML ファイルを分割して、より分かりやすくすることもできそうです。

可読性も非常に高く、素晴らしいツールだと思います🥳🎉

参考

コラボスタイル Developers

Discussion