APIのテストツールrunnを使ってみた

こんにちは。
PHR事業開発部の村越です。

今日は、APIの自動テストをRunnを使用して作成してみたので、Runnの使い方などを紹介しようと思います。

runnとは?

runnとは、@k1Lowさんの作成されているAPIのシナリオテストツールです。
runn

NOBORIアプリの開発で、APIの自動テストを作成することになり、ツールを検討する際に出会い、とても良いと感じたので使い方などを紹介させて頂こうと思います。

導入方法

runnを使ったテスト方法には
・CLIツールを動かす
・Goのテストコードに組み込んで使う
の2種類がありますが、本記事では前者の「CLIツールを動かす」の方で説明したいと思います。

リリースバイナリをダウンロードする

サポートしているOSであればこちらからダウンロードして使用できます。

Homebrew、Goでインストールする

コマンドラインから、下記のコマンドでインストールすることができます。(HomebrewやGoが入っていることが前提です)
ただし、Windowsでgo installをしても、インストールのシェルの都合上、途中で失敗してしまうようなので、こちらの方法ではインストールできないようです。Windowsでは後述のdockerでのインストールをおすすめします。

brew install k1LoW/tap/runn
or
go install github.com/k1LoW/runn/cmd/runn@latest

dockerイメージを使う

dockerイメージが提供されているので、そちらを使うこともできます。

docker container run -it --rm --name runn -v $PWD:/books ghcr.io/k1low/runn:latest run /books/*.yml
※Windowsの場合、$PWDは使用できないので、各自のrunn作業フォルダを指定するようにしてください。

コードの説明

テストコードの作成にはyamlファイルを使用します。
yamlファイルの基本的な記載の仕方を説明します。

desc: テストケースの説明

debug: true # デバッグログを出力するか

# 定数の設定
vars:
  userName: "のぼり始"
  
# runnerの設定
runners:
  req:
    endpoint: https://example # APIのベースパス

# テストケースの処理
steps:
  setUserName:
    desc: 処理の説明
    req:
      /setUserName: # APIのパス
        post:
          body:
            application/json:
              userName: "{{vars.userName}}" # varsで定義した値を入れる
    test:
      steps.checkup_list.res.status == 200  # テスト結果の比較

上記のように
varsに定数
runnersに接続先の情報
stepsに処理
といった記載をします。それぞれについて、下記で説明します。

vars

varsには定数を記載します。最初の例のように、定数を入れることもできますが、下記のようにjsonファイルを取得することもできます。

vars:
  reqJson: "json://req.json" # リクエストのJSONデータの読み込み
  resJson: "json://req.json" # レスポンスのJSONデータの読み込み
  constJson: "json://const.json" # 定数のJSONデータの読み込み

steps:
  setUserName:
    desc: 処理の説明
    req:
      /setUserName: # APIのパス
        post:
          body:
            application/json: "{{ vars.reqJson}}" # リクエストに読み込んだJSONデータを使用
    test:
      steps.checkup_list.res.status == 200
      && compare(steps.setUserName.res.body, vars.resJson) # レスポンスと読み込んだJSONデータを比較

  setUserName:
    desc: 処理の説明
    req:
      /setUserName: # APIのパス
        post:
          body:
            application/json:
              userName: "{{vars.constJson.userName}}" # JSONファイルの値を取り出して使用することも可能
JSONファイルサンプル

{
  "userName": "のぼり始"
}

runners

runnersには、処理の接続先の情報を記載します。
上記例の「req」は別の変数に変えることも可能です。例えば、接続先A、Bがあり、一度のテストケースでそれぞれと通信したい場合は

# runnerの設定
runners:
  req_A:
    endpoint: https://exampleA # 接続先Aのベースパス
  req_B:
    endpoint: https://exampleB # 接続先Bのベースパス

# テストケースの処理
steps:
  testA:
    req_A:
      /test:
        ・・・
  testB:
    req_B:
      /test:
        ・・・

のように記載することができます。

steps

stepsには、実際のテストケースを記載します。
steps内に複数のテストを記載でき、レスポンスを次のテストケースから参照することも簡単にできます。

steps:
  createUser:
    desc: ユーザーの作成。レスポンスでユーザーのIDを取得する
    req:
      /createUser: # APIのパス
        post:
          body:
            application/json:
              userName: "のぼり始"
    test:
      steps.checkup_list.res.status == 200
  getUserInfo:
    desc:
    req:
      /getUserInfo:
        post:
          body:
            application/json:
              id: {{ steps.createUser.res.body.id }} # ユーザー作成のレスポンスからIDを取得
    test:
      steps.checkup_list.res.status == 200

その他の記載方法

runnには上記の記載方法以外にも、テストを楽にする記載方法があります。
以降はそちらの説明をしたいと思います。

シナリオの再利用

include構文を使用することで、シナリオの再利用が可能になります。

# setUserName.yaml

steps:
  setUserName:
    desc: 処理の説明
    req:
      /setUserName:
        post:
          body:
            application/json: "{{ vars.reqJson}}"
    test:
      steps.checkup_list.res.status == 200
      && compare(steps.setUserName.res.body, vars.resJson)
# test_setUserName.yaml

steps:
  setUserName:
    desc: setUserNameをincludeで実行
    include:
      path: setUserName.yaml
      vars:
        reqJson: "json://req.json"
        resJson: "json://res.json"

setUserNameの処理を別ファイルにして、includeで読み込んで実行するようにしました。
setUserName.yaml側で、reqJson、resJsonが定義されていませんが、include側でvarsの指定をされているため、指定されたjsonファイルが使用されて処理が実行されます。
また、参照先、参照元で同じ名前でvarsが定義されてあった場合、参照元の値が使用されます。そのため、参照元側にvarsを定義しておくことで、varsを指定した場合のみ指定された値を使い、指定しなかった場合はデフォルト値の参照元に記載された値が使用されるといった使い方もできます。

SSL証明書を使用してリクエストを送信する

SSL証明書を使用してリクエストを送る必要がある場合、以下のように記載します。

runners:
  req:
    endpoint: https://example
    cacert: /books/Certificate/cacert.pem
    cert: /books/Certificate/cert.crt
    key: /books/Certificate/key.key

DBからデータを参照する

runnでは、APIでのリクエスト以外に、DBと直接接続してデータを取ってくることができます。

runners:
  req:
    endpoint: https://example
  db: pg://dbuser:password@hostname:port/dbname # DBの接続先情報

steps:
  dbRequest:
    desc: ユーザーの取得
    db:
      query: SELECT * FROM USERS; # 実行するSQLを記載

  getUserInfo:
    req:
      /getUserInfo:
        post:
          body:
            application/json:
              id: {{ steps.dbRequest.rows[0].id }} # DBから取得したユーザーIDを指定
    test:
      steps.checkup_list.res.status == 200

他のAPIテストツールにはない便利な機能です。
リクエストやレスポンスなどから取得できないデータを使用したい場合などに、DBから値を取得して使うことができるので、テストの幅が広がると思います。

テストの繰り返し

loop構文を使用することで、テストの繰り返しを行うことができます。例えば、リクエスト、レスポンスの配列を用意しておき、loopさせることで同じテストのリクエスト、レスポンスが違うテストパターンを簡単に記載することができます。

vars:
  requestJson:
     - "req1.json"
     - "req2.json"
     - "req3.json"
  responseJson:
     - "res1.json"
     - "res1.json"
     - "res1.json"

steps:
  setUserName:
    desc: setUserNameをリクエストの数分繰り返す
    loop:
      count: len(vars.requestJson)
    include:
      path: setUserName.yaml
      vars:
        reqJson: "json://{{ vars.requestJson[i] }}"
        resJson: "json://{{ vars.responseJson[i] }}"

JSONのリクエスト、レスポンスを動的に変える

Go Templateの機能を利用して、JSONのリクエスト、レスポンスの値を動的に変えることができます。
使い方は、jsonファイルの拡張子を".json.template"にするだけです。
ただし、現状利用できる場所がinclude構文でjsonファイルを指定する場合だけとなっております。そのため、動的に変えたい場合は別ファイルにする必要があります。

# test_setUserName.yaml

steps:
  setUserName:
    desc: setUserNameをincludeで実行(テンプレート使用)
    include:
      path: setUserName.yaml
      vars:
        reqJson: "json://req.json.template"
        resJson: "json://res.json.template"
req.json.template

{
  "userName": "{{.steps.getUserName.res.body.name}}", # レスポンスから取得
  "userName2": "{{with index .steps.getUserName.rows 0 }}{{ .userName}}{{ end }}", # DBから取得
  "userName3": "{{.vars.userName}}" # vars
}

参考資料

runn github
runn クックブック

最後に

今回は、APIテストツールのrunnについて紹介しました。
他のAPIテストツールと比べて、比較的新しいツールで開発が活発に行われている印象ですので、今後も使いやすいようアップデートされていくのではと思っています。
便利なツールを作成頂いた@k1Lowさんに感謝します。

今回は以上となります。
今後とも「PSP」をよろしくお願い致します。

村越