LLMを利用して、APIを自動でテストするツールを作ってみる

Sreake事業部

2024.8.15

1. はじめに

はじめまして、Sreake事業部の井上 秀一です。私はSreake事業部にて、SREや生成AIに関するResearch & Developmentを行っています。本記事では、LLMとテストツールを用いて、自動でAPIをテストするツール構築して検証しました。

2. TL;DR

本記事では、生成AI(LLM)を利用してAPIを自動でテストするツールを構築しました。OpenAPI Specificationを基に、テストケースを自動で生成して、e2eテストツールを利用して自律的にAPIをテストします。

構築とテストフロー

  1. OpenAPI Specificationの取得: APIの仕様を収集
  2. 仕様の分割: LLMが誤解しないようにAPIリソースごとに分割
  3. FunctionCall定義の生成: APIリソースごとにFunctionCallの定義を作成
  4. LLMによるレビュー: テスト計画の生成
  5. 実際の入力生成: テスト計画に基づいて入力データを生成

作成したアプリケーションを利用してテストを行う事はできましたが、テスト計画の精度や、データベースとの整合性、テストの柔軟性が課題として挙げられます。

3. システムのシーケンスと全体の流れ

上記図の各コンポーネントの説明します。

  • API Service:
    • openapi.jsonを取得できるAPIサーバ
  • llm-e2e-generator (main.py):
    • 本検証で処理を行うメインの機構です。
    • 各コンポーネントを呼び出し、処理を行います。
  • Review agent:
    • LLMを利用して、APIのテスト戦略をOpenAPI Specificationから生成する機構です。
  • input-generating agent:
    • LLMを使用してAPI入力のグループを生成する機構です。
    • 生成されたテスト戦略JSONから、FunctionCallを使用して実際のAPI入力を生成します。
  • dredd:
    • 生成されたAPI入力を使用して、APIをテストする機構です。
  • json:
    • 各ステップで生成されたjsonです。

このシステムは入力に、テスト対象のAPIサーバのURLとOpenAPI Specificationを与え、自律的にテスト行う事を目的としています。

4. APIを対象としたE2Eテストと、実現するツール

ここではAPIを対象としたE2Eテスト手法の説明と、E2Eテストを行うためのツールについて解説します。

E2Eテスト

  • E2Eテストとは、APIの全体的な機能と振る舞いを、実際のユーザーシナリオに基づいてテストする手法です。APIの入力から出力まで、エンドポイント間の連携を含めた一連の流れを検証します。テスト方法やその方針は様々ですが、一例を以下に示します。
    • 複数のAPIエンドポイントを連続して呼び出し、全体的な機能を確認する
    • 実際の環境に近い状態でテストを実行する
    • データの整合性や期待される結果を検証する
    • パフォーマンスや信頼性などを確認する
  • また、より具体的なテスト手法として、ブラックボックステストがあります。ブラックボックステストは、内部実装を参照することなく、検証を行い、期待される結果と実際の結果を比較する手法を指します。ISTQBテスト技術者資格制度Foundation Level シラバスによれば、ブラックボックステスト技法として、以下の四点があげられています。

同値分割法

同値分割法は、入力データを有効な同値クラスと無効な同値クラスに分割するテスト技法です。同値クラスとは、プログラムが同じように処理すると想定される入力値の集合です。この技法を使用することで、テストケースの数を減らしつつ、効果的にテストを行うことができます。

境界値分析

境界値分析は、同値クラスの境界値や境界値の直前・直後の値をテストする技法です。多くの欠陥は境界値付近で発生しやすいため、この技法は効果的です。例えば、1から100までの有効な入力範囲がある場合、0、1、100、101などの値をテストします。

デシジョンテーブルテスト

デシジョンテーブルテストは、システムの条件の組み合わせと、それに対応する動作を表形式で表現し、テストケースを作成する技法です。複雑な業務ロジックや条件分岐を持つシステムのテストに特に有効です。全ての可能な条件の組み合わせをカバーすることで、漏れのないテストを行うことができます。

状態遷移テスト

状態遷移テストは、システムの異なる状態間の遷移をテストする技法です。システムの可能な状態、状態間の遷移、遷移を引き起こすイベント、および遷移の結果として実行されるアクションを特定します。この技法は、状態を持つシステム(例:予約システム、ユーザーインターフェース)のテストに特に有効です。

一般的なE2Eテストツールの紹介

ここでは製作期間中について利用・調査した、二つのE2Eテストツールを紹介します。 E2EテストはCurlコマンドやPythonのrequestライブラリを用いて行う事も実現できますが、E2Eテストツールを使う事で、より簡単に行う事ができます。

Run N(runn)

  • 概要
    • Run Nはシナリオに基づきテストを実行するためのCLIツールです。Run Nを使用することで、HTTPとgRPCを用いたAPIやWebサービスのテストを自動化できます。 テスト内容の定義には、YAMLで記述したシナリオファイルを使用します。このシナリオファイルは、OpenAPI Specificationのような構文で記述できるのが特徴です。
      ただし、2024/07/14時点で、Run Nの機能として、OpenAPI Specificationからシナリオファイルを自動生成する様な機能は提供されていないため、自力で書く必要があります。
      開発者目線であれば、CI/CDと統合し、テストを効率的に実行できるようになります。 またRun Nは、日本語版のチュートリアルクックブックが用意されています。

Dredd (本検証で利用)

  • 概要
    • Dreddは、API BlueprintOpenAPI Specification(Version 2.0, Version 3.0.0)を読み取り、これらのドキュメント通りにAPIが実装されているか検証するためのCLIツールです。動作は非常にシンプルで、APIの定義ファイルとAPIのエンドポイントを与える事で、自動的にテスト行う事が出来ます。また、OpenAPI Specificationの強力なバリデーションチェックも内包しているため、不完全なOpenAPI Specificationを弾く事ができます。
  • hooks
    • Dreddでは、hooksと呼ばれる機能は用意されています。hooksを利用する事で、APIテスト時の前後で任意のコードブロックを実行する事ができます。
    • hooksで利用できる言語
      • JavaScript
      • Go
      • Perl
      • PHP
      • Python
      • Ruby
      • Rust
    • hooksの種類
      • beforeAll: テスト全体の実行前
      • beforeEach: 各HTTPトランザクションの前
      • before: 単一のHTTPトランザクションの前
      • beforeEachValidation: 各HTTPトランザクションが検証される前
      • beforeValidation: 単一のHTTPトランザクションが検証される前
      • after: 単一のHTTPトランザクションの後
      • afterEach: 各HTTPトランザクションの後
      • afterAll: テスト全体の実行後にすべてのHTTPトランザクション

本記事でやるテスト

  • 本記事は、LLMとDreddをを利用して、ブラックボックステストが行えるかに焦点を絞ります。API毎のテストケースに応じた入力と出力をLLMに生成させます。

5. テストケース生成までのフローと概要

上記図はテストケース生成までのフローを示しています。以下に各ステップの概要を示します。

  • OpenAPI Specificationの取得:
    • OpenAPI仕様書を取得するステップ。APIの構造やエンドポイントの情報を収集します。
  • OpenAPI SpecificationをAPIリソース毎に切り出す:
    • 取得したOpenAPI仕様書を、個々のAPIリソースに分割します。
    • 後続のステップで、利用するLLMがハルシネーションを起こさないようにするために必要な工程です。
  • APIリソース毎にFunctionCallの定義を生成する:
    • 各APIリソースに対して、FunctionCallの定義を作成します。これにより、各リソースの具体的な呼び出し方法が明確になります。
  • APIリソース毎のLLMのレビュー(LLMにテスト計画を生成させる):
    • 各APIリソースのFunctionCall定義をレビューし、大規模言語モデル(LLM)を用いてテスト計画を生成します。
  • APIリソース毎のFunctionCalling定義とテスト計画を基に、実際の入力をLLMに生成する:
    • テスト計画を基に、LLMを使って実際の入力を生成し、FunctionCallingの実行を確認します。

6. OpenAPI Specification (旧Swagger Specification)

前章で説明したRun N(runn)とDreddは、扱う上で、OpenAPI Specificationが必要です。

また、LLMにテストケースを生成する上で、OpenAPI Specificationの内部構造を知る必要があります。 したがって、ここでは、OpenAPI Specificationの説明と、内部構造について説明します。

概要

  • OpenAPI Specification (旧Swagger Specification)は、REST APIを記述するための標準規格です。以下に特徴を示します:
    1. APIの仕様をYAMLまたはJSON形式で記述できるフォーマットです。
    2. APIのエンドポイント、HTTPメソッド、リクエストパラメータ、レスポンス、認証方法などを定義できます。
    3. プログラミング言語に依存せず、人間とコンピュータの両方が理解できる形式です。
    4. APIの設計、開発、ドキュメント作成、テスト、クライアントコード生成などに活用できます。
  • OpenAPI Specificationを使用することで、以下のようなメリットがあります:
    • APIの設計と実装の一貫性を保てる
    • 自動的にドキュメントを生成できる
    • クライアントSDKの自動生成が可能
    • APIのモックサーバーを簡単に作成できる
    • テストケースの自動生成ができる
  • 本記事におけるOpenAPI Specificationの必要性を以下に示します:
    • APIテストツールを利用に必要
    • LLMがテスト対象のAPIを理解し、テストケースを生成する際に必要

書き方・作り方

一から書くこともできますが、Swagger Editorを利用してGUIでAPI仕様を作成できます。 また、PythonであればFastAPI、JavaであればSpring Bootとspringdoc-openapi-uiを利用する事で、APIをコード上で定義するだけで、OpenAPI Specificationを意識せずとも自動で生成する事ができます。

チュートリアルと内部構造の簡単な説明

  • 何故内部構造まで理解する必要があるのか?
    • LLMを利用したテストケースの生成時にOpenAPI Specificationが必要になるからです。 ここでは、FaseAPIを利用して、OpenAPI Specificationを自動で生成し、内部構造について簡単に説明します。

APIサーバの作成

まず、PythonとFastAPIを利用してシンプルなAPIを定義します。 FastAPIのコードを以下に示します。python3 server.pyでサーバが立ち上がります。

# server.py
from typing import Annotated, List, Optional

import uvicorn
from fastapi import FastAPI, HTTPException, Path
from pydantic import BaseModel

app = FastAPI()

# dbの代わり
items = [
    {
        "id": 1,
        "name": "Item1",
        "description": "This is an item description.",
    },
    {
        "id": 2,
        "name": "Item2",
        "description": "This is another item description.",
    },
]


# データモデルの定義
class Item(BaseModel):
    id: int
    name: str
    description: Optional[str] = None


# 全アイテムの取得
@app.get("/items", response_model=List[Item])
def read_items():
    return items


# アイテムの更新
@app.put("/items/{item_id}", response_model=Item)
def update_item(
    item: Item,
    item_id: Annotated[
        int,
        Path(
            ...,
            title="This indicates the item number. The number must be between 1 and 100.",
            example=1,
        ),
    ],
):
    for index, old_item in enumerate(items):
        if old_item["id"] == item_id:
            items[index] = item.model_dump()
            return item
    raise HTTPException(status_code=404, detail="Item not found")


def excute():
  # 8080ポートでサーバが立ち上がる
    uvicorn.run(app, host="0.0.0.0", port=8080)


if __name__ == "__main__":
    excute()

OpenAPI Specificationの取得と確認

curl http://localhost:8080/openapi.json > openapi.jsonでOpenAPI Specificationを取得します。

openapi.jsonの中身を確認します。 ここで、一部の要素が"$ref": "#/components/schemas/Item"の様に$#で構成されている箇所が存在します。 これらはJSON Referenceと呼ばれ、共通のリソースを複数回利用する目的で使用されています。 次ステップではJSON Referenceのデコード方法について示します。

{
    "openapi": "3.1.0",
    "info": {
        "title": "FastAPI",
        "version": "0.1.0"
    },
    "paths": {
        "/items": {
            "get": {
                "summary": "Read Items",
                "operationId": "read_items_items_get",
                "responses": {
                    "200": {
                        "description": "Successful Response",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "items": {
                                        "$ref": "#/components/schemas/Item" # JSON Reference
                                        ...省略

JSON Referenceのデコード方法

Pythonとjsonrefを利用する事でJSON Referenceをデコードする事ができます。 python3 decode.pyを実行して、生成されたdecoded_openapi.jsonを確認してください。 $#で構成されるJSON Referenceが、全てデコードされています。

# decode.py

import json

import jsonref

def decode(input_path: str, output_path: str) -> dict:

    if input_path is None:
        raise ValueError(
            "The path to the file is None. Please provide a valid file path."
        )

    with open(input_path, "r") as file:
        decoded_data = jsonref.loads(file.read())
        with open(output_path, "w") as file:
            json.dump(decoded_data, file)

if __name__ == "__main__":
    decode("./openapi.json", "./decoded_openapi.json")

OpenAPI Specificationの構成を確認します。

最後にOpenAPI Specificationを扱い、それぞれの要素を取り出します。


# extract.py

import json
from typing import Dict

def read_openapi() -> Dict:
    with open("decoded_openapi.json", "r") as file:
        data = json.load(file)
    return data

if __name__ == "__main__":
    openapi_dict = read_openapi()

    for path, api_resourses in openapi_dict["paths"].items():
        for api_method, api_details in api_resourses.items():
            print(f"Path: {path}, Method: {api_method}")
            # その他の処理
            # Ex) LLMにパースする処理等を書くとか

今回のチュートリアルであれば、以下の様に出力されます。

Path: /items, Method: get
Path: /items/{item_id}, Method: put

以下のコードを追加すれば、特定のAPIリソースだけを抽出する事ができます。

# 特定のAPIの要素を取得する
api_spec = openapi_dict["paths"]["/items/{item_id}"]["put"]
print(api_spec["requestBody"])
print(api_spec["parameters"])

7. LLM

本記事ではLLM(ChatGPT)を利用して、APIのテストケースを作成して、Dreddで利用できるようにする必要があります。 つまり、LLMの入出力を整理すると次のように「想定」されます。

  • 入力:
    • テストケースの要望
    • OpenAPI Specification
  • 出力:
    • APIへの入力(パスパラメータの値、リクエストボディ等)

しかし、実際の所、完璧に生成するというのは困難です。 生成AIが現実に存在しない情報を生成してしまうハルシネーションや、関係ない文言を含ませてしまい、正確なテストを行う以前に、OpenAPI Specificationの定義と合致しない入力が生成されます。 従って、ハルシネーションを低減させるような工夫が必要です。

FunctionCalling

FunctionCallingは、OpenAIが提供する機能で、大規模言語モデル(LLM)と外部ツールを連携させるための強力な機能です。この機能を使用することで、モデルは特定の関数を呼び出すべきかどうかを判断し、適切なJSONオブジェクトを生成して関数の引数を提供することができます。

具体的にはGPT4等のAPIに、入力としてプロンプトとFunctionCallingを定義したスキーマを与えます。以下にFunctionCallingのスキーマ例を示します

プロンプト

item_idが20の入力を生成して

FunctionCalling定義スキーマ

[
    {
        "type": "function",
        "function": {
            "name": "delete_item_items__item_id__delete",
            "description": "Delete Item",
            "parameters": {
                "type": "object",
                "properties": {
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "item_id": {
                                "type": "integer",
                                "title": "This indicates the item number. The number must be between 1 and 100."
                            }
                        },
                        "required": [
                            "item_id"
                        ]
                    }
                }
            }
        }
    }
]

FunctionCallingの出力としては、以下のようなレスポンスを得ることができます。

FunctionCallingの出力例

{
    "delete_item_items__item_id__delete": {
        "parameters": {
            "item_id": 20
        },
    }
}

FunctionCallingは、あくまで内容(値)を定義されたフォーマットに落とし込むので、具体的な値を示す必要があると考えます。

また、本検証では、OpenAPI SpecificationからAPIへの入力(テストケース)を生成するため、OpenAPI SpecificationからAPIへの入力をFunctionCallの定義スキーマに変換する仕組みが必要です。この作業は、OpenAI Cookbookに掲載されているFunction calling with an OpenAPI specificationを利用して行います。

LLMに役割分担させる(エージェント化)

LLMに複雑なプロンプトや、達成難易度が高いタスクをLLMに与えると、解答の安定性や、所望の出力を欠く事が課題になっています。この課題は、LLMのマルチエージェント化を行う事で、改善できる事がCrewAI等で期待されています。CrewAIでは、LLMのマルチエージェント化を通して、タスク分解を行い、より効率的に所望の出力を生成させる取り組みを行っています。また、本検証では、所望のテストケースを一発で生成できない事象が実際に起きていました。

前章のFunctionCalling定義スキーマを利用した例

ブラックボックステスト行ってください。
{
    "delete_item_items__item_id__delete": {
        "parameters": {
            "item_id": 1 # 特に意味もなく1になりがち
        },
    }
}

従って、「ブラックボックステスト行ってください。」というプロンプトと共に、一度にテストケースの出力を生成させるのではなく、二つのステップに分割してそれぞれをエージェントと模して利用します。

  • Reviewer Agent
    • 役割
      • 単一のOpenAPI Specificationからテスト計画を作るエージェント
      • どのようなテストを行うかの戦略やパラメータ値の設定指針を生成する
    • エージェントに出力させる項目
      • instructions_for_input_parameters: パスパラメータに関するテスト指示
      • instructions_for_input_request_body: リクエストbodyに関するテスト指示
      • expected_response_code: このテストケースで期待できるレスポンスコード
  • input-generating agent
    • 役割
      • テスト計画から実際にAPIへの入力を生成する
    • エージェントの出力
      • OpenAPI Specificationに基づく入力

8. テストしてみる

ここでは実際にテストを実行してログベースで説明を行います。

サーバを用意

本検証では簡単なFastAPIサーバを用意しました。

また、item_idの仕様として、1-100のレンジという制約を説明として設けた一方で、実際にはValidationをしていません。 これは実装の不一致を意図的に作り出し、実際にテストを行う過程で、不具合が見つかることを期待しているためです。

# OpenAPI Specificationの中身抜粋
{
    "name": "item_id",
    "in": "path",
    "required": true,
    "schema": {
        "type": "integer",
        "title": "This indicates the item number. The number must be between 1 and 100.",
    },
    "example": 1,
}

テスト (準備フェーズ)

> Environment variables "API_URL" and "OPENAPI_URL" are not set.
> Prepare a test server.
> Resume after 1 seconds.
✔  Simplified server for testing is starting up. (0:00:01.02)
✔  Get openapi.json
> Output will be stored in "./outputs/FastAPI_v_0.1.0_openapi_3.1.0"
✔  Workspace Name
✔  Save openapi.json
✔  Get openapi.json
> Output will be stored in "./outputs/FastAPI_v_0.1.0_openapi_3.1.0"
✔  Workspace Name
✔  Save openapi.json
> Checking the effectiveness of openapi.json. pre-test initiated.
✔  Check validity of openapi.json.
  • テストを実行すると準備フェーズに入ります。
  • 準備として、openapi.jsonの取得、openapi.jsonが構造的に有効化チェックを行なっています。

OpenAPI Specification -> 各種Jsonの生成

> GET | /items | Create openapi.json for each resource.
> GET | /items | Convert to function call schema.
> GET | /items | The openapi schema is under review.
> GET | /items | Generate input to the API from review results.
> POST | /items | Create openapi.json for each resource.
> POST | /items | Convert to function call schema.
> POST | /items | The openapi schema is under review.
> POST | /items | Generate input to the API from review results.
> GET | /items/{item_id} | Create openapi.json for each resource.
> GET | /items/{item_id} | Convert to function call schema.
> GET | /items/{item_id} | The openapi schema is under review.
> GET | /items/{item_id} | Generate input to the API from review results.
> PUT | /items/{item_id} | Create openapi.json for each resource.
> PUT | /items/{item_id} | Convert to function call schema.
> PUT | /items/{item_id} | The openapi schema is under review.
> PUT | /items/{item_id} | Generate input to the API from review results.
> DELETE | /items/{item_id} | Create openapi.json for each resource.
> DELETE | /items/{item_id} | Convert to function call schema.
> DELETE | /items/{item_id} | The openapi schema is under review.
> DELETE | /items/{item_id} | Generate input to the API from review results.
> GET | /restore | Create openapi.json for each resource.
> GET | /restore | Convert to function call schema.
> GET | /restore | The openapi schema is under review.
> GET | /restore | Generate input to the API from review results.
✔  Json resource generation in progress.
  • 準備フェーズが完了すると、HTTPメソッドとリソースごと処理が行われます。
    • HTTPメソッドとリソースごとに単一のOpenAPI Specificationを作成する
    • 単一のOpenAPI SpecificationをFunctionCall定義スキーマに変換
    • 単一のOpenAPI Specificationからテスト戦略を作成する
    • テスト戦略からAPI入力を作成する

各種APIのテスト

> GET | /items | Test case (0) | Result: pass | test-title: Test Case 1: Get Items - Normal Case
> GET | /items | Test case (1) | Result: fail | test-title: Test Case 2: Get Items - Error Case
✔  GET | /items | API test done.
> POST | /items | Test case (0) | Result: fail | test-title: Create item with valid data
> POST | /items | Test case (1) | Result: pass | test-title: Create item with missing required fields
> POST | /items | Test case (2) | Result: pass | test-title: Create item with invalid data type
✔  POST | /items | API test done.
> GET | /items/{item_id} | Test case (0) | Result: pass | test-title: Get Item with valid item_id
> GET | /items/{item_id} | Test case (1) | Result: fail | test-title: Get Item with invalid item_id
> GET | /items/{item_id} | Test case (2) | Result: error | test-title: Get Item with no item_id
✔  GET | /items/{item_id} | API test done.
> PUT | /items/{item_id} | Test case (0) | Result: pass | test-title: Update Item Success Case
> PUT | /items/{item_id} | Test case (1) | Result: fail | test-title: Update Item with Invalid Item ID
> PUT | /items/{item_id} | Test case (2) | Result: pass | test-title: Update Item with Missing Required Fields in Request Body
✔  PUT | /items/{item_id} | API test done.
> DELETE | /items/{item_id} | Test case (0) | Result: pass | test-title: Delete Existing Item
> DELETE | /items/{item_id} | Test case (1) | Result: fail | test-title: Delete Non-existing Item
> DELETE | /items/{item_id} | Test case (2) | Result: fail | test-title: Delete Item with Invalid Item ID
✔  DELETE | /items/{item_id} | API test done.
> GET | /restore | Test case (0) | Result: pass | test-title: Test Case 1: Get Restore
> GET | /restore | Test case (1) | Result: fail | test-title: Test Case 2: Validate Error Handling
✔  GET | /restore | API test done.
  • HTTPメソッドとリソース毎に生成されたテストケースを用いて、各APIをテストします。
  • Resultは三種類あります。
    • pass: テストが期待通り行われたことを示します
    • fail: テストが期待通りに行われなかった (期待しているレスポンスコードではなかった)
    • error: テストケースの装填に失敗した (テストに必要な要素が足りない等、GPTの幻覚由来にまつわるエラー)

テスト集計

Test Results
total: 16
pass: 8     # OKだったテストケース
fail: 7     # NGだったテストケース
error: 1    # dredd内部でテストケースの準備中にクラッシュ
  • テストが完了すると集計が行われます。

failとerrorの調査

テスト結果、failとerrorが多発していることがわかりました。 ここでは、failとerrorの原因を調査して、考察を行いました。 ただし、重複箇所があるため抜粋します。

LLMが生成した計画が不適切

そもそもGET /itemsは、特定のアイテムリクエストをするメソッドではないため、LLMが不適切な計画を生成しています。

> GET | /items | Test case (1) | Result: fail | test-title: Test Case 2: Get Items - Error Case

テストケースの説明: このテストケースは、存在しないアイテムがリクエストされたときに、GET /items API が正しいエラー・レスポンスを返すことを確認するためのものです。

APIへの入力

{
    "read_items_items_get": {
        "responses": {
            "response_code": 404
        },
        "test_title": "Test Case 2: Get Items - Error Case",
        "test_overview": "This test case is to confirm that the GET /items API returns the correct error response when a non-existent item is requested."
    }
}

レスポンスログ

{
    "errors": [
        {
            "message": "Expected status code '404', but got '200'."
        }
    ]
}

APIサーバが実装と異なるケース

これはテストで、仕様書上不可能なリクエストを送信し、通らないはずなのに通ってしまった例で、本アプリケーションの恩恵を受けた例です。

> GET | /items/{item_id} | Test case (1) | Result: fail | test-title: Get Item with invalid item_id

テストケースの説明: このテストケースは、無効な item_id が指定された場合のシステムの応答をチェックするためのものです。

APIへの入力

{
    "read_item_items__item_id__get": {
        "parameters": {
            "item_id": 101
        },
        "responses": {
            "response_code": 422
        },
        "test_title": "Get Item with invalid item_id",
        "test_overview": "This test case is for checking the system's response when an invalid item_id is provided."
    }
}

レスポンスログ

{
    "errors": [
        {
            "message": "Expected status code '422', but got '200'."
        }
    ]
}

パスパラメータを指定しないテストケース

こちらはパスパラメータでitem_id指定しなかったため、dreddにテストケースを装填するタイミングで失敗しています。現状、OpenAPI Specificationをdreddに装填するため、OpenAPI Specificationで必須となっているパスパラメータが存在しない場合、装填処理でエラーを出します。

> GET | /items/{item_id} | Test case (2) | Result: error | test-title: Get Item with no item_id

テストケースの説明: このテストケースは、item_id が提供されない場合のシステムの応答をチェックするためのものです。

APIへの入力

{
    "read_item_items__item_id__get": {
        "responses": {
            "response_code": 422
        },
        "test_title": "Get Item with no item_id",
        "test_overview": "This test case is for checking the system's response when no item_id is provided."
    }
}

データベース(DB)との整合性が取れず失敗するケース

DBには既に値が存在しているため、期待値と異なる結果が返る

> POST | /items | Test case (0) | Result: fail | test-title: Create item with valid data

desc: 有効なデータが提供されたときに、APIがアイテムを作成できるかどうかを検証するテスト

APIへの入力

{
    "create_item_items_post": {
        "requestBody": {
            "id": 1,
            "name": "item1",
            "description": "This is item1"
        },
        "responses": {
            "response_code": 200
        },
        "test_title": "Create item with valid data",
        "test_overview": "Test to verify if the API is able to create an item when provided with valid data"
    }
}

レスポンスログ

"body": {"detail": "Item with this ID already exists"}

以上をまとめると次のような課題が考えられます。

  • 不適切なテスト計画の生成
  • データベースとの整合性
  • 柔軟性に欠けるテスト(OpenAPI Specificationとdredへの依存)

9. まとめ

本検証では、LLMを活用してAPIを自動でテストするツールの構築と検証を行いました。以下に成果と課題をまとめます。

成果

  1. 自動テストツールの構築: LLMとDreddを組み合わせた自動テストツールを構築できました
  2. テストケースの生成: LLMを利用して、OpenAPI Specificationからテスト戦略やテストケースを自動生成するプロセスを確立しました。
  3. 実際のテスト実行: 構築したツールを用いて、実際のAPIサーバに対して自動テストを実行し、いくつかのテストケースで期待通りの結果を得ることができました。

課題

  1. 不適切なテスト計画の生成: LLMが生成するテスト計画が不適切な場合があり、APIの特定の機能を正確にテストできないことがありました。
  2. データベースとの整合性: テストの実行時にデータベースとの整合性が取れず、テストケースで失敗することがありました。
  3. 柔軟性の欠如: OpenAPI SpecificationとDreddに依存しているため、特定の要件に対して柔軟なテストを行うことが難しい場合がありました。

結論

LLMを活用したAPIの自動テストツールは、効率的なテスト自動化の一歩となりましたが、現状の課題を克服するためには、さらなる改良が必要です。今後も研究と開発を続け、より高精度かつ柔軟な自動テストツールの実現を目指します。

ブログ一覧へ戻る

お気軽にお問い合わせください

SREの設計・技術支援から、
SRE運用内で使用する
ツールの導入など、
SRE全般についてご支援しています。

資料請求・お問い合わせ