初心者向け: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を進めていけるものと思います。
お問合せはお気軽に
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:UnsplashのGoran Ivos