見出し画像

初心者向け:APIテストツール「Tavern」の基本


はじめに


こんにちは。SHIFTのテスト自動化エンジニアの松岡です。
以前の記事*で、APIテストツールであるTavernで使えるテクニックを紹介いたしましたが、今回は改めてTavernそのものについて紹介したいと思います。

*参考
API自動テストツールTavernによるレスポンス配列のアサーション
API自動テストツールTavernにおけるリクエストパラメータの動的生成
【テスト自動化】JenkinsでTavernを使用したAPIテスト構築

Tavern概要


TavernはAPIのテストに使用できるテストツールです。
pytestをベースに構成されており、基本的なAPI実行、項目のチェックであれば、プログラミングレスで実行できるのが特徴です。

Tavernの特徴


テストシナリオをyamlファイルで実装するため、基本的なテストであればプログラミングレスで行うことができ、可読性も高いのが特徴です。

Tavernの主な機能


  • APIの実行

  • APIステータスコードの確認

  • APIレスポンスの確認

  • python関数の呼び出し

インストール方法


pip3 install tavern

pipで簡単にインストールできます。 (pipコマンドが実行できない場合はpythonがインストールされていないかもしれませんので、そちらを先にインストールしてください。)

テストシナリオの作成


簡単なテストであれば1つのyamlファイルのみで完結できます。 ここでは例としてJSONPlaceholderのAPIをGETメソッドで呼び出してみます。

## test_sample1.tavern.yaml
test_name: APIテスト

stages:
  - name: Get Tavern Sample
    request:
      url: "https://jsonplaceholder.typicode.com/posts/1"
      method: GET
      headers:
         Accept: "application/json"
    response:
      strict: false
      status_code: 200
      json:
         id: 1

各項目の使い方

  • test_name テスト名を入力します。テスト名は実行結果に表示されます。

  • stages テストの内容を記述します。
    stages は name, request, responseの3つの要素が1つのまとまりとして構成されます。例えば、①アクセストークンを取得してから、②テスト対象のAPIを実行するような場合、name, request, responseがそれぞれ2つずつ記述されます。

  • name 各テストステップの内容を記述します。基本的にはhttpメソッドと、呼び出すAPIの内容を記載しおくとよいでしょう。

  • request 呼び出すAPIに送るリクエストの内容

  • request.url URLを記述します。URLには、パスパラメータ、クエリパラメータを記述することもできます。

  • request.method GET, POST, PUT, PATCH, DELETEのいずれか

  • request.headers リクエストヘッダを設定できます。

  • response APIからのレスポンスに対するアサーション、値の変数への代入などができます。

  • response.strict true または false を指定します。 ※後述

  • response.status レスポンスコードの期待値を記載できます。上記の例では、200以外のレスポンスがAPIから帰ってきた場合に、テスト結果NGとなります。

  • response.json レスポンスのアサーションを行うことができます。ここでは id の値が 1 であることを確認していますが、では他のレスポンスはチェックしないのでしょうか?
    こたえは先程出てきた strict の値によって異なります。
    strict が true の場合、 { "id" : "1" } 以外のJSONが返されると、たとえ id の値があっていてもテスト結果はNGになりますが、 false の場合は、テスト結果OKとなります。
    このように response.strict の設定と組み合わせることにより、JSON全体を完全一致でテストするのか、特定の項目のみアサーションを行うか、使い分けることができます。

実装と実行

pytestコマンドのパラメータに作成したyamlファイルを指定することで実行できます。今回はPowerShellから実行してみます。

PS C:\Users\naoto.matsuoka\Desktop\tavern> pytest .\test_sample_1.tavern.yaml
================================================= test session starts ==================================================
platform win32 -- Python 3.12.4, pytest-7.2.2, pluggy-1.5.0
rootdir: C:\Users\naoto.matsuoka\Desktop\tavern
plugins: tavern-2.11.0
collected 1 item

test_sample_1.tavern.yaml .                                                                                       [100%]

================================================== 1 passed in 0.90s ===================================================
PS C:\Users\naoto.matsuoka\Desktop\tavern>

NGの場合

ではテストが失敗した場合はどうなるでしょうか? 元のyamlファイルの status_code: 200 これを、 status_code: 500 こう書き換えて実行してみます。

PS C:\Users\naoto.matsuoka\Desktop\tavern> pytest .\test_sample_1.tavern.yaml
================================================= test session starts ==================================================
platform win32 -- Python 3.12.4, pytest-7.2.2, pluggy-1.5.0
rootdir: C:\Users\naoto.matsuoka\Desktop\tavern
plugins: tavern-2.11.0
collected 1 item

test_sample_1.tavern.yaml F                                                                                       [100%]

======================================================= FAILURES =======================================================
_______________________ C:\Users\naoto.matsuoka\Desktop\tavern\test_sample_1.tavern.yaml::APIテスト _______________________
Format variables:

Source test stage (line 3):
  - name: Tavern Sample
    request:
      url: "https://jsonplaceholder.typicode.com/posts/1"
      method: GET
      headers:
         Accept: "application/json"
    response:
      strict: false
      status_code: 500
      json:
         id: 1
      save:
        json:

Formatted stage:
  name: Tavern Sample
  request:
    headers:
      Accept: application/json
    method: GET
    url: https://jsonplaceholder.typicode.com/posts/1
  response:
    json:
      id: 1
    save:
      json:
        test_id: id
    status_code: 500
    strict: false

Errors:
E   tavern._core.exceptions.TestFailError: Test 'Tavern Sample' failed:
    - Status code was 200, expected 500
-------------------------------------------------- Captured log call ---------------------------------------------------
ERROR    tavern.response:response.py:44 Status code was 200, expected 500
=============================================== short test summary info ================================================
FAILED test_sample_1.tavern.yaml::APIテスト
================================================== 1 failed in 0.69s ===================================================
PS C:\Users\naoto.matsuoka\Desktop\tavern>

NGの場合上記のように出力されます。 今回の例ではStatusが期待値と異なりましたが、

Errors:
E   tavern._core.exceptions.TestFailError: Test 'Tavern Sample' failed:
    - Status code was 200, expected 500

このように、エラー箇所をTavernが教えてくれます。

POSTの場合

次はmethodがPOSTのAPIを実行してみます。

## test_sample2.tavern.yaml
test_name: API POSTテスト

stages:
  - name: POST Tavern Sample
    request:
      url: "https://jsonplaceholder.typicode.com/posts"
      json:
        title: 'foo'
        body: 'bar'
        userId: 1
        flg: true
        object: {
          foo: 'bar',
          hoge: 'fuga'
        }
        array: [
          {foo:'bar'},
          {foo:'hoge'}
        ]
      method: POST
      headers:
         Accept: "application/json; charset=UTF-8"
    response:
      strict: false
      status_code: 201

GETとの大きな違いとして、request.jsonの項目があります。 上記の例では、下記のようなJSONオブジェクトがリクエストBodyとして送信されます。

{
  "title" : "body",
  "body" : "bar",
  "userId" : 1,
  "flg" : true,
  "object": {
    "foo" : "bar",
    "hoge" : "fuga"
  },
  "array": [
    { "foo" : "bar" },
    { "foo" : "hoge" }
  ]
}

別のyamlファイル読み込み

ここではテストデータとシナリオを別々のyamlファイルに記載してみます。

## test_sample_3.tavern.yaml
test_name: 別ファイル変数読み込み

includes:
  - !include test_dev.yaml

stages:
  - name: POST Tavern Sample
    request:
      url: "https://jsonplaceholder.typicode.com/posts"
      json:
        title: 'foo'
        body: 'bar'
        userId: !int "{test_var.id}"
      method: POST
      headers:
         Accept: "application/json; charset=UTF-8"
    response:
      strict: false
      status_code: 201

「POSTの場合」で紹介したyamlとほぼ同じですが、stagesの前に項目が増えています。 include 読み込む別ファイル群 !include 読み込む別ファイル。 include の下に複数の !include を指定できます。 読み込んでいるyamlファイルは以下の通り。

## test_dev.yaml
name: 共通変数
description: Dev環境

variables:
  test_var:
    id: 1

variables 以下に.tavern.yamlに読み込ませたい変数名と値を記載します。
読み取る側のファイルでは、中括弧 {}で括ることで、変数を読み込むことができます。
読み込んだ変数はパスパラメータ、リクエストJSON、レスポンスチェック時の期待値などで使用できます。(上記の例では一度読み込んだ後、そのままで文字列になってしまうため、 !intを付加して数値型にキャストしています。)
シナリオは同じでも環境ごとに異なるテストデータを使用する必要がある場合は、このように複数ファイルの構成にすることでテストケースの記載をテストデータに依存しないようにすることができます。

同ファイル内値受け渡し

では同じyamlファイル内で変数を使用することは可能でしょうか? 下記のyamlファイルをご覧ください。

## test_sample_4.tavern.yaml
test_name: APIテスト変数設定

stages:
  - name: Tavern Sample Step 1
    request:
      url: "https://jsonplaceholder.typicode.com/posts/1"
      method: GET
      headers:
         Accept: "application/json"
    response:
      strict: false
      status_code: 200
      json:
         id: 1
      save:
        json:
          test_id: id

  - name: Tavern Sample Step 2
    request:
      url: "https://jsonplaceholder.typicode.com/posts/{test_id}"
      method: GET
      headers:
         Accept: "application/json"
    response:
      strict: false
      status_code: 200
      json:
         title: "sunt aut facere repellat provident occaecati excepturi optio reprehenderit"

このテストでは2ステップ構成になっています。Step 1で、以下の項目を追加しています。

  • save レスポンスの項目を任意の変数に格納することができます。ここではレスポンス json の test_id を変数 id に格納しています。そしてStep 2の url のパスパラメータで、変数を参照しています。 上記の例ではあまりピンと来ないかもしれませんが、呼び出しにaccess tokenによる認証が必要なAPIのテストで効力を発揮します。

データ駆動テストの実施

次はデータ駆動テストの紹介です。 以下のファイルをご覧下さい。
POSTの場合で紹介したケースに手を加えたものになります。

## test_sample_2-2.tavern.yaml
test_name: APIテスト

marks:
  - parametrize:
      key:
        - title
        - user_id
        - expected_status
      vals:
        - ["title1", 100, 201]
        - ["title2", 200, 201]
        - ["title3", 300, 201]

stages:
  - name: POST Tavern Sample
    request:
      url: "https://jsonplaceholder.typicode.com/posts"
      json:
        title: "{title}"
        body: "bar"
        userId: !int "{user_id}"
        flg: true
        object: {
          foo: 'bar',
          hoge: 'fuga'
        }
        array: [
          {foo:'bar'},
          {foo:'hoge'}
        ]
      method: POST
      headers:
         Accept: "application/json; charset=UTF-8"
    response:
      strict: false
      status_code: !int "{expected_status}"
  • marks parametrizeと組み合わせることにより、データ駆動テストが実行可能です。

  • parametrize 配下にkeyとvals の組み合わせを指定することにより、複数データで同一テストを繰り返し行うことができます。また、parametrizeはmarks配下に複数指定できます。

  • key データを格納する変数を宣言します。

  • vals 宣言したkeysに対応する値の配列を定義します。配列の要素数はkeysで宣言した変数の個数と一致する必要があります。

このテストを実行してみます。

PS C:\Users\naoto.matsuoka\Desktop\tavern> pytest .\test_sample_2-2.tavern.yaml
================================================= test session starts =================================================
platform win32 -- Python 3.12.4, pytest-7.2.2, pluggy-1.5.0
rootdir: C:\Users\naoto.matsuoka\Desktop\tavern
plugins: tavern-2.11.0
collected 3 items

test_sample_2-2.tavern.yaml ...                                                                                  [100%]

================================================== 3 passed in 2.79s ==================================================
PS C:\Users\naoto.matsuoka\Desktop\tavern>

ログのcollected 3 itemsというメッセージに注目して下さい。yamlファイル内のテストケースは1つですが、ちゃんとテストデータの分だけ3回実行されています。

その他の機能

他にも下記のような機能があります。詳しくは割愛

  • 外部関数呼び出し:pythonの関数を呼び出して、より詳細な処理(特殊なレスポンスのアサーション、レクエストの動的生成)

  • API呼び出し待機:非同期処理を行うAPIをテスト対象にする場合など、決められた時間API呼び出しを待つことができます。待機時間は任意で決められます。

利点と欠点


プログラミングレス

さて、ここまで見て頂いた通り、様々なテストを、pythonのコードを書くことなく実行できました。Tavernのテストケースは可読性が非常に高いのが特徴です。

レスポンスxml

Tavernは基本的にレスポンスがjson形式で返ってくることを前提に構成されています。プレーンテキストであれば外部関数化からアサーションの処理を行うことも可能ですが、レスポンスxmlを対象としたAPIテストでは他のツールを導入することを検討したほうが良いでしょう。

継続的インテグレーションへの活用

本記事冒頭で紹介しましたが、インストールが簡単で、さらにDockerと組み合わせて実行すれば、様々なサーバ上で、ローカルの環境に依存することなく、テストを実行できます。これはJenkinsなどのCIツールからのテストジョブとしての呼び出しも用意にすることを意味します。 また、Allureと組み合わせて実行することも可能ですので、Jenkins上でテスト結果を確認する際の見やすさにも直結します。

まとめ


万能のテストツールは少ないですが、テスト対象に合わせたものを選定することで、より質の高いテスト計画、CI/CDを進めていけるものと思います。

参考:Tavern公式ドキュメント


執筆者プロフィール:松岡 直人
SIerでのJava、jqueryの開発経験を経て、SHIFTに入社。 SHIFTでは入社当初より画面/APIテスト自動化に関わる。 「人間が最低限しか頑張らない自動化」を目指している。

お問合せはお気軽に
https://service.shiftinc.jp/contact/

SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/

SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/

SHIFTの導入事例
https://service.shiftinc.jp/case/

お役立ち資料はこちら
https://service.shiftinc.jp/resources/

SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/

PHOTO:UnsplashGoran Ivos