見出し画像

API自動テストツールTavernによるレスポンス配列のアサーション

こんにちは。SHIFTのテスト自動化エンジニアの松岡です。

Tavernは、yamlに期待値を記載するだけで、APIのレスポンスJSONのアサーションを行うことができるテストツールです。

ただし、APIの仕様によっては、レスポンスBodyが配列になる場合もあると思います。ここでは外部関数をpythonで定義して、上記2つのレスポンスアサーションの実装方法を紹介したいと思います。



◆ディレクトリ構成

testFolderhelper
│   └ testing_utils.py …… 外部関数を記述するpythonです
└ test
    ├ __init__.py …… yamlからpythonを呼ぶために必要なファイル。
    └ test_foo_bar.tavern.yaml …… テストケース

yamlと同じ階層に「__init__.py」を配置します。そうすることで、テスト実行時に、yamlを格納しているディレクトリの親ディレクトリから配下のディレクトリを参照してpythonを呼び出すことができます。(__init__.pyの役割とpythonの呼び出しについてはここでは割愛します。)


◆アサーション実装

例として、以下のようなレスポンスBodyを返すAPIを対象に、ユニークキーである項目idの値が”bar”のレコードの各項目をアサーションしたいときの実装の仕方を説明します。

[
 {
  id: ”foo”,
  hoge: 100,
  fuga: “test”
 },
 {
  id: “bar”,
  hoge: 200,
  fuga: “testtest”
 }
]
※レスポンスBody全体が配列

¶test_foo_bar.tavern.yaml

- name: API Response Array Test
request:
  url: “https://apitest/array?foobar=123”
  method: GET
response:
  status_code: 200
  verify_response_with:
    - function: helper.testing_utils:assert_array ……※1
     extra_kwargs: ……※2
       unique_key_name: “idunique_key_value: “barexpected_obj_array:
         [
           { expected_key: ‘hoge’, expected_value: 200 },
           {expected_key: ‘fuga’, expected_value: ‘testtest’}
         ]

responseのブロックにて、「verify_response_with」を記載することで、API実行後にpythonの外部関数を呼び出すことができます。

functionの項目で、呼び出す関数を定義します(※1)。ここでは“helperディレクトリ”にある、“testing_utils”というpythonファイルの、“assert_array”関数を呼び出しています。

「extra_kwargs」では関数に渡す引数を定義します(※2)。

渡す引数の数と名称は、“assert_array”関数の定義と一致している必要があります。

ここでは“unique_key_name”, “unique_key_value”, “expected_obj_array”の3つを渡しています。

・unique_key_name:レスポンス配列のレコードを一意に決めるユニークキーのキー名

・unique_key_value:上記ユニークキーの値

・expected_obj_array:ユニークキーで検索してヒットしたレコードに対し、アサーションを行いたいキー名とその値。expected_keyとexpected_valueの組み合わせでそれぞれ指定。

上記yamlの例では「id=”bar”」のレコードが、「hoge=200」かつ「fuga=”testtest”」であることが期待値となります。


¶testing_utils.py

def assert_array(response, unique_key_name, unique_key_valule, expected_obj_array):
   result = False
   res_array = response.json()

   # レスポンスが配列かどうか
   if not isinstance(res_array, list):
       assert False

   for res in res_array:
       if unique_key_name in res:
           if res[unique_key_name] == uniquekey_value:
               result = True
               
               for i in range(len(expected_obj_array)):
                   expected_obj = expected_obj_array[i]

                   key_name = expected_obj[‘expected_key’]
                   expected_value = expected_obj[‘expected_value’]

                   if not key_name in res:
                       assert False
                   else:
                       actual_value = res[key_name]
                       if not actual_value == expected_value:
                           assert False
               break
   assert result

python全体は以上のようになります。

各部分ごとの解説を以下に記載します。

def assert_array(response, unique_key_name, unique_key_valule, expected_obj_array):……(1)
   result = False
   res_array = response.json()……(2)

   # レスポンスが配列かどうか
   if not isinstance(res_array, list): ……(3)
       assert False
   for res in res_array:
       if unique_key_name in res:
           if res[unique_key_name] == uniquekey_value: ……(4)
               result = True
~~(中略)~~
               break ……(5)
   assert result ……(4)

(1) yamlで定義した外部関数の名称、引数と同じものを定義します。 ただし、引数の1つ目は固定で「response」とします。この変数にはAPIのレスポンス全体が自動的に渡されます(yamlでの定義不要)。

(2) response.json()を変数に格納することで、レスポンスBody全体を取得できます。「json」とありますが、レスポンスBodyが配列の場合でもこの記述を使用します。

(3) 変数「res_array」に格納したレスポンスBodyが配列かどうか確認し、配列でない場合はアサーションエラーとします。

(4) 配列の各オブジェクトに、アサーション対象(yamlの例ではid=”bar”)のものが存在するかチェックしています。2行目のFalseで初期化した変数にTrueを代入し、関数の最後でアサーションしています。 アサーション対象のオブジェクトが配列に存在しない場合は、配列のオブジェクト数分for文を繰り返し、if文の中に入っていかないことで、最終的にアサーションエラーとなるようにしています。

(5) アサーション対象のレコードが存在してチェック処理を行う場合、以降の繰り返し処理は不要なので、breakします。

                for i in range(len(expected_obj_array)): ……(6)
                   expected_obj = expected_obj_array[i] ……(6)

                   key_name = expected_obj[‘expected_key’] ……(7)
                   expected_value = expected_obj[‘expected_value’] ……(7)

                   if not key_name in res: ……(8)
                       assert False
                   else:
                       actual_value = res[key_name] ……(9)
                       if not actual_value == expected_value: ……(9)
                           assert False
               break

(6) レスポンス配列の繰り返し処理内で、今度は期待値を格納しているオブジェクトの配列分、繰り返し処理を行い、実際の値のアサーションをします。

(7) キー名/期待値の組み合わせが格納されたオブジェクト「expected_obj」から、キー名と期待値を変数に代入します。

(8) キーが存在しなければアサーションエラー

(9) キーが存在する場合に、実際の値と期待値が異なっていればアサーションエラー


まとめ

yamlでの指定を変えれば、APIごとに異なった外部関数を呼ぶことも可能ですが、上記のような関数を定義することでレスポンスのタイプに応じたレスポンスアサーションの処理を汎用的にコーディングすることができます。


※ エディタの都合上インデントがずれてしまう場合があります。コピペ時にお手元でご確認ください。


【この公式ライターのほかの記事も読む】

API自動テスト_レスポンスアサーションのコーディングルール化のすすめ

_________________________________

執筆者プロフィール:松岡 直人                    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/