生成AIのソフトウェアテストへの活用 ――“使えるテストコード”生成するプロンプト文と出力の現在地

本稿は書籍Javaエンジニアのための ソフトウェアテスト実践入門Appendix 2をWeb記事として公開したものです。

2022年後半に登場したChatGPTを皮切りに、生成AIは瞬く間に世の中に浸透しました。現在では世界中の投資がこの分野に向けられており、新しいサービスも続々と登場しています。

生成AIが最もその価値を発揮するシーンの一つが、システム開発における活用です。例えばコーディングのアシストやレビュー、ログの分析、画面モックの作成など、生成AIの活用はすでに実用的な段階にあります。

本稿ではその中でも、⁠生成AIをいかにしてテストに活用するか」という点にフォーカスを当てます。

1. 本稿で取り上げる生成AI

生成AIと一口に言ってもさまざまな種類がありますが、その代表は何といってもOpen AI社のChatGPTでしょう。

ChatGPTは、大規模言語モデル(LLM)とチャット形式で対話するためのWebサービスです。本稿で示すさまざまな事例は、ChatGPTの中でも本稿執筆時点の最新モデルである"gpt4"を前提にしています。

なお、ChatGPTが汎用的な利用を目的としたツールであるのに対して、エンジニアリングに特化した生成AIツールにGitHub Copilotがあります。

GitHub Copilotは、VS Codeの拡張機能(プラグイン)として提供され、テストを効率化するための高度な機能が備わっています。

ただし本稿の範囲の中でGitHub Copilotの機能をカバーするのは、分量や時間軸の観点から難しかったため、スコープ外としている点をご了承ください。

生成AIをテストに活用するためのアプローチ

本稿では、ChatGPTによってテストコードを生成するための方法をケース別に見ていきます。すべてのケースに通底するのが、テストコードを生成するためのアプローチには以下の二つがある、というものです。

アプローチ①
まず開発者がテスト仕様やテストケースを作成し、それをインプットにして、ChatGPTにテストコードを生成させる。
アプローチ②
完成されたプロダクションコードをインプットにして、ChatGPTにテストコードを生成させる。
図1 生成AIをテストに活用するためのアプローチ
図1

両者のうち、正攻法と言えるのは①の方です。このアプローチは、プロダクションコードを所与としてないため、テスト駆動開発に近い考え方と見なすこともできます。

ただし、テスト仕様を生成AIが理解できるようにテキストベースの情報(プロンプト)を作成するには、相応にコストがかかります。またウォーターフォール開発ではむしろ、先にプロダクションコードを作成し、後からテストコードを作成する方が一般的です。

このように考えると、②のアプローチの方が場合によっては効率的かもしれません。しかし②のアプローチには、重大な落とし穴があります。そもそもテストコードとは、開発者自身が作成したプロダクションコードが、仕様通りに実装されていることを検証するために作成するものです。その点、プロダクションコードをChatGPTのインプットにすると、プロダクションコードに何らかの不備があった場合に、生成されるテストコードがそれに引きずられて精度が落ちてしまう、という可能性を排除できないのです。

したがって②のアプローチを採用する場合は、生成されるテストコードを鵜呑みにするのではなく、開発者自身がきちんと精査するというのが大前提です。少なくとも検証のためのコードは、開発者自身で記述した方がよいでしょう。

さて、この二つのアプローチに共通して言えるのは、生成AIによるテストには、効率化の観点で「損益分岐点」があるという点です。

アプローチ①であれば、ChatGPTのために、テスト仕様やテストケースをわざわざプロンプトとして作ることが求められます。またアプローチ②の場合も、開発者自身が生成されたコードを精査したり、検証コードを追記したりする必要があります。

いずれのアプローチも、下手をすると生成AIを使わずに開発者自身がゼロからテストコードを作成した方が、効率性の観点で上回る可能性があるのです。

本稿では、JUnit単体テスト、REST APIテスト、UIテスト、負荷テストといったケースで、生成AIによるコード生成の事例を紹介します。いずれのケースも、この「損益分岐点」をしっかりと見極める必要があります。

本稿の事例を参考にすれば、見極めのためのポイントを理解することが可能になるでしょう。

2. JUnit単体テストでの活用

テスト仕様からテストコード生成(アプローチ①)

この項では、ChatGPTを活用して、JUnitの単体テストコードを生成する事例を紹介します。

ここではまず、アプローチ①、すなわちテスト仕様やテストケースからテストコードを生成するアプローチを取り上げます。テスト対象クラスの題材は、書籍の2.1.7項で登場したFeeServiceクラス(振込手数料を計算するためのクラス)です。

例えば以下のようなプロンプト(Markdown形式)を作成し、ChatGPTに投入してみましょう。

以下の[制約条件]とテスト対象ユニットごとの[テストケース]に従い、単体テストを行います。
Javaのテストコードを生成してください。

# 制約条件

* テスティングフレームワークには、JUnit 5を利用してください。
* テスト対象クラスは`pro.kensait.junit5.fee.FeeServiceTest`です。
* テストクラスは`pro.kensait.junit5.fee.FeeServiceTest`とします。
* @BeforeEachが付与された前処理において、FeeServiceクラスのインスタンスを生成し、feeServiceフィールドに割り当てます。
* テストメソッド名は英語で、コメントは日本語でお願いします。

# テスト対象ユニット共通の情報

* OUR_BANK_CODE(自分の銀行の銀行コード):"B001"
* OTHER_BANK_CODE(他の銀行の銀行コード):"B999"

# テスト対象ユニット

* 期待される振る舞い
指定された銀行コードと振込金額から、手数料を計算して返す。

* インタフェース
`int calcFee(String bankCode, int amount)`

* テストケース
|振込先の銀行|振込金額|手数料|
|---|---|---|
|自分の銀行|30000円以上|0円|
||30000円未満|100円|
|他の銀行|40000円以上|200円|
||40000円未満|500円|

すると、ChatGPTによって以下のようなテストコードが生成されます。

public class FeeServiceTest {
    private FeeService feeService;
    // 前処理:FeeServiceのインスタンス生成
    @BeforeEach
    void setUp() {
        feeService = new FeeService();
    }
    // 自分の銀行に対する振込で、30000円以上の場合の手数料テスト
    @Test
    @DisplayName("Test calcFee with our bank and amount >= 30000")
    void testCalcFeeOurBankAboveThreshold() {
        assertEquals(0, feeService.calcFee("B001", 30000),
                     "自分の銀行への振込で30000円以上の場合、手数料は0円であるべき");
    }
    // 自分の銀行に対する振込で、30000円未満の場合の手数料テスト
    @Test
    @DisplayName("Test calcFee with our bank and amount < 30000")
    void testCalcFeeOurBankBelowThreshold() {
        assertEquals(100, feeService.calcFee("B001", 29999),
                     "自分の銀行への振込で30000円未満の場合、手数料は100円であるべき");
    }
    ........
}

生成されたこのテストコードには、プロンプトに記述したテストケースに則り、4つのテストメソッドが実装されています。

このコードは、FeeServiceクラスのテスト要件を完全に満たしており、生成結果の評価としては「100点」と言って差し支えないでしょう。

プロダクションコードからテストコード生成(アプローチ②)

次にアプローチ②、すなわちプロダクションコードからテストコードを生成するアプローチを取り上げます。

テスト対象クラスの題材は、書籍の2.2.1項で登場したShippingServiceクラス(⁠⁠荷物配送サービス」するためのクラス)です。前項のFeeServiceと比較すると、依存先クラスが多いという点がポイントです。具体的には引数としてBaggage、Client、呼び出し先としてCostCalculatorIFがあり、それぞれをモック化する必要がある、という点に難しさがあります。

ShippingServiceのテスト仕様を、これらの依存先クラスを意識して作成するのはコストが高いので、プロダクションコードをそのまま使ってみましょう。具体的には、以下のようなプロンプト(Markdown形式)をChatGPTに投入します。

以下の[制約条件]に従い、単体テストを行います。
テストコードを、生成してください。

# 制約条件

* テスティングフレームワークには、JUnit 5を利用してください。
* モッキングフレームワークには、Mockitoを利用してください。
* テスト対象クラスは`pro.kensait.java.shipping.ShippingService`です。
* テストクラスは`pro.kensait.java.shipping.ShippingServiceTest`とします。
* @BeforeEachが付与された前処理において、ShippingServiceクラスのインスタンスを生成し、shippingServiceフィールドに割り当てます。
* 以下のクラスをモック化し、カバレッジを意識した振る舞いを実装してください。
	* Baggage
	* Client
	* CostCalculatorIF
* 検証は開発者自身で行うため、コードの生成は不要です。
* テストメソッド名は英語で、コメントは日本語でお願いします。
* すべてのテストメソッドを生成してください。

# テスト対象クラス
....プロダクションコードを貼り付ける....

# 依存先クラス
....プロダクションコードを貼り付ける....

このようにテスト対象クラスはもちろんのこと、依存先クラスのコードもプロンプトに追加する必要があるため、ひと手間かかります。また、プロンプトに入力する情報量も増えるため、ChatGPTの文字数制限である「トークンリミット」に抵触してしまう可能性も高まります。

このプロンプトをChatGPTに投入すると、以下のようなコードが生成されます。

public class ShippingServiceTest {
    private ShippingService shippingService;
    @Mock
    private CostCalculatorIF costCalculator;
    @Mock
    private Client client;
    @Mock
    private Baggage baggage1;
    @Mock
    private Baggage baggage2;
    @BeforeEach
    public void setUp() {
        MockitoAnnotations.openMocks(this);
        shippingService = new ShippingService(costCalculator);
    }

    @Test
    public void testOrderShipping_GeneralClient() {
        // 前提条件
        when(client.clientType()).thenReturn(ClientType.GENERAL);
        when(client.originRegion()).thenReturn(RegionType.HONSHU);
        when(baggage1.baggageType()).thenReturn(BaggageType.SMALL);
        when(baggage2.baggageType()).thenReturn(BaggageType.LARGE);
        when(costCalculator.calcShippingCost(
                BaggageType.SMALL, RegionType.HONSHU)).thenReturn(1000);
        when(costCalculator.calcShippingCost(
                BaggageType.LARGE, RegionType.HONSHU)).thenReturn(1500);
        List<Baggage> baggageList = Arrays.asList(baggage1, baggage2);

        // テスト実行
        shippingService.orderShipping(client, LocalDate.now(), baggageList);

        // 検証
        verify(costCalculator).calcShippingCost(
                BaggageType.SMALL, RegionType.HONSHU);
        verify(costCalculator).calcShippingCost(
                BaggageType.LARGE, RegionType.HONSHU);
    }

    @Test
    public void testOrderShipping_GoldClient_DiscountApplied() {
        // 前提条件
        when(client.clientType()).thenReturn(ClientType.GOLD);
        when(client.originRegion()).thenReturn(RegionType.HONSHU);
        when(baggage1.baggageType()).thenReturn(BaggageType.SMALL);
        when(baggage2.baggageType()).thenReturn(BaggageType.LARGE);
        when(costCalculator.calcShippingCost(
                BaggageType.SMALL, RegionType.HONSHU)).thenReturn(2000);
        when(costCalculator.calcShippingCost(
                BaggageType.LARGE, RegionType.HONSHU)).thenReturn(1500);
        List<Baggage> baggageList = Arrays.asList(baggage1, baggage2);

        // テスト実行
        shippingService.orderShipping(client, LocalDate.now(), baggageList);

        // 検証
        verify(costCalculator).calcShippingCost(
                BaggageType.SMALL, RegionType.HONSHU);
        verify(costCalculator).calcShippingCost(
                BaggageType.LARGE, RegionType.HONSHU);
    }

    @Test
    public void testOrderShipping_DiamondClient_DiscountApplied() {
        // 前提条件
        when(client.clientType()).thenReturn(ClientType.DIAMOND);
        when(client.originRegion()).thenReturn(RegionType.HONSHU);
        when(baggage1.baggageType()).thenReturn(BaggageType.SMALL);
        when(baggage2.baggageType()).thenReturn(BaggageType.LARGE);
        when(costCalculator.calcShippingCost(
                BaggageType.SMALL, RegionType.HONSHU)).thenReturn(3000);
        when(costCalculator.calcShippingCost(
                BaggageType.LARGE, RegionType.HONSHU)).thenReturn(2000);
        List<Baggage> baggageList = Arrays.asList(baggage1, baggage2);

        // テスト実行
        shippingService.orderShipping(client, LocalDate.now(), baggageList);

        // 検証
        verify(costCalculator).calcShippingCost(
                BaggageType.SMALL, RegionType.HONSHU);
        verify(costCalculator).calcShippingCost(
                BaggageType.LARGE, RegionType.HONSHU);
    }
}

このテストコードに点数をつけるとしたら「70点」といったところでしょうか。

実はこのコードのコンパイルは問題なく通りますし、このコードを実行するとすべてのテストが成功します。ただしテストコードとしては、要件を充足しているとは言えません。このコードには、次のような2つの課題があります。

課題①⁠検証コードの追加が必要

まず前述したように、ChatGPTによって生成されたテストコードに対して、検証のためのコードを開発者自身で実装する必要があります。

今回のプロンプトでは「検証は開発者自身で行うため、コードの生成は不要です」と明記しているにも関わらず、生成されたコードにはMockitoによるコミュニケーションベースの検証コードが含まれています。

この部分は特に「悪さ」をするものではないので、残しておいても問題ありませんが、これだけでは検証コードとして不十分です。

テスト対象クラスであるShippingServiceは副作用を発生させるため、以下のような状態ベースの検証コードを追加します。

Shipping actual = ShippingDAO.findAll().get(0); //【1】
assertEquals(3150, actual.totalPrice()); //【2】

ここではShippingDAOが、十分に品質が確保された信頼できるクラスであるという前提の下、ShippingDAOによって実測値を取得します【1⁠⁠。そしてassertEquals()を呼び出して、期待値と実測値が一致していることを検証します【2⁠⁠。

このとき「期待される配送料が3150円であること」を、なぜ開発者が決めることができたのでしょうか。これは生成された各テストコードの「前提条件」の部分で、ChatGPTがどのような振る舞いをモックに設定したのかを読み解くことによって、導き出した値です。

もしChatGPTを使わず、開発者が自分の意図でモックの振る舞いを設定するのであれば、そのコンテキストの中で、期待値を自分で決めることは比較的容易なはずです。ところがChatGPTに振る舞いの設定を委ねると、生成された振る舞いを理解した上で、⁠正しいプロダクションコードだったらこういった値が期待されるはずだ」という計算を開発者が自分で行わなければなりません。

この点は、このアプローチの難易度を上げている一つのポイントになるのではないかと思います。

課題②⁠カバレッジが不十分

ChatGPTによって生成された既出のテストコードは、カバレッジが不十分です。以下に、このテストを実行したときのカバレッジレポート(テスト対象クラスShippingServiceorderShipping()の部分)を示します。

図2 カバレッジレポートの表示
図2

このようにゴールド会員、ダイヤモンド会員ともに、割引後の下限金額に抵触したというケースがカバーされていません。そこで筆者の方で、改めて以下のようにプロンプトに投入してみました。

ゴールド会員、ダイヤモンド会員ともに、「割引したものの下限金額に抵触してそれ以上割引されない」というケースがカバーされていません。
これらのケースもカバーしたテストコードを再度生成してください。

ところが本稿執筆時点では、このプロンプトからは、要件を充足するようなテストコードは生成されませんでした。この課題を解決するためには、モデルの進化を待つか、プロンプトエンジニアリングのさらなる工夫が必要になるものと思われます。

3. REST APIテストでの活用

RestAssuredによるテストコード生成

ここでは、ChatGPTを活用して、REST APIのためのテストコードを生成する事例を紹介します。テストコードは、JUnit 5とRestAssuredを使用して作成するものとします。

REST APIの外部仕様は「OpenAPI仕様ファイル」[1]として記述することができるため、これをインプットにして、ChatGPTによってテストコードを生成させてみましょう。

「OpenAPI仕様ファイル」は一種のテスト仕様とも言えるので、これは既出のアプローチ①に該当します。

今回テスト対象となるのは、Person(人物)というリソースに対するREST APIです。このREST APIの仕様は、以下のような「OpenAPI仕様ファイル」で定義されているものとします。

openapi: 3.0.0
info:
  title: Person API
  version: 1.0.0
servers:
  - url: http://localhost:8080
paths:
  /persons/{personId}:
    get:
      operationId: getPersonById
      tags:
        - Persons
      parameters:
        - name: personId
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Person'
        '404':
          description: Person not found
    post:
      operationId: createPerson
      tags:
        - Persons
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Person'
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Person'
        '400':
          description: Invalid input
    put:
      ........
    delete:
      ........
  /persons/query_by_age:
    get:
      operationId: queryByLowerAge
      tags:
        - Persons
      parameters:
        - name: lowerAge
          in: query
          required: true
          schema:
            type: integer
      responses:
        '200':
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Person'
components:
  schemas:
    Person:
      type: object
      properties:
        personId:
          type: integer
        personName:
          type: string
        age:
          type: integer
        gender:
          type: string
      required:
        - personName
        - age
        - gender

この「OpenAPI仕様ファイル」を利用して、ChatGPTに以下のようなプロンプトを投入します。

以下の[OpenAPI仕様ファイル]に示したREST APIがあるものとします。
[制約条件]に従い、テストコードを生成してください。

# 制約条件

* テスティングフレームワークには、JUnit 5とRestAssuredを利用してください。
* テストクラスは`pro.kensait.spring.person.rest.test.PersonApiTest`とします。
* REST APIの呼び出し結果は`io.restassured.response.Response`として取得し、レスポンスボディはJUnitのアサーションAPIで検証するものとします。
* テストメソッド名は英語で、コメントは日本語でお願いします。
* すべてのテストメソッドを生成してください。

# OpenAPI仕様ファイル
....既出のOpenAPI仕様を貼り付ける...

このようなプロンプトをChatGPTに投入すると、JUnit 5とRestAssuredによるテストクラスのコードが生成されます。返されるコードはここでは省略しますが、当該REST APIのためのテスト要件を充足した、非常に精度が高いものです。

このケースがうまくいく理由は、REST APIのテスト仕様として「OpenAPI仕様ファイル」をそのまま使うことができ、必要な情報がそこに集約されているという点にあると言えるでしょう。

WireMockによるモックサーバーのコード生成

テストとは少し趣旨が変わりますが、ここで取り上げるのはWireMockによるモックサーバーです。

前項と同じ、Personリソースを対象にしたREST APIの「OpenAPI仕様ファイル」をインプットに、WireMockのコードを生成させてみましょう。以下のようなプロンプトを、ChatGPTに投入します。

以下の[OpenAPI仕様ファイル]に示したREST APIがあるものとします。
[制約条件]に従い、モックサーバーのコードを生成してください。

# 制約条件

* モックサーバーは、WireMockを利用します。
* モックサーバーのテストクラスは`pro.kensait.spring.person.rest.test.WireMockApp`とします。

# OpenAPI仕様ファイル
....既出のOpenAPI仕様を貼り付ける...

このようなプロンプトをChatGPTに投入すると、WireMockによるモックサーバーのコードが生成されます。生成されるコードはここでは割愛しますが、当該REST APIの振る舞いを忠実に再現した、そのまま動作するモックサーバーが簡単に出来上がります。

もちろん、引数マッチングや返すレスポンスに関しては生成AIに情報を与えていないため、生成されたコードに対して後から開発者が補う必要があります。ただし、それを差し引いても、大幅に効率化できる点は間違いありません。

4. SelenideによるUIテストでの活用

この項では、ChatGPTを活用してUIテストコードを生成する事例を紹介します。

UIテストの題材は、書籍の7.1.3項で登場した「テックブックストア」というWebアプリケーションです。

図3 テックブックストアのページ遷移
図3

生成するテストコードは、JUnit 5+Selenideによるものとします。書籍に登場したBookStoreTestクラスが一種の模範解答になります。

ここでは既出のアプローチ①に則り、まずUI操作や検証内容を表す「テストシナリオ」を作成し、それをインプットにしてChatGPTにUIテストコードを生成させます。

具体的には、以下のようなプロンプトを投入します。

以下の[制約条件]および[テストシナリオ]に従い、WebブラウザのUIテストを行います。
SelenideとJUnit 5によるJavaのテストコードを、生成してください。

# 制約条件

* パッケージ名は`pro.kensait.selenium.bookstore`、クラス名は`BookStoreTest`とします。
* ベースURLとしてhttp://localhost:8080を設定してください。
* リンクはID属性を指定してクリックしてください。
* ID属性は、"#"で表現してください。
* 要素(HTMLタグ)は、Selenideの"$$"を使って取得してください。
* タイトルの検証には、JUnit 5のアサーションAPIを使ってください。
* タイトル以外の検証には、Selenideの検証API(`shouldHave()``shouldBe()`など)を使ってください。
* タイトルの検証した直後に、ページのスクリーンショットを保存してください。
  保存名は、"番号-ページタイトル"とします。

# テストシナリオ

|番号|指示|ID|場所|VALUE|
|:--|--|--|--|--|
|1|オープン|||http://localhost:8080/|
|2|検証||title()|TopPage|
|3|入力|email||[email protected]|
|4|入力|password||password|
|5|クリック|loginButton||loginButton|
|6|リダイレクト|||/toSelect|
|7|検証||title()|BookSelectPage|
|8|検証|book-table|ボディ内の1行目の1列目|Java SEディープダイブ|
|9|検証|book-table|ボディの行数|34|
....中略....
|36|検証||title()|OrderSuccessPage|
|37|クリック|logoutButton|||
|38|検証||title()|FinishPage|

「テストシナリオ」の内容は、既出のものと同様です。ここではExcel等の表計算ソフトで作成し、それをプロンプトに貼り付けやすくするためにMarkdown形式に変換しています。

図4 Excelで作成したテストシナリオ
図4
※クリックして拡大して表示できます。

このようなプロンプトを投入すると、JUnit 5とSelenideによるUIテストコードが生成されます。その内容はここでは割愛しますが、模範解答となるBookStoreTestクラスとほぼ同様で、そのまま動作可能な非常に精度が高いものです。

注目していただきたいのが、テストシナリオの「番号8」の行です。この部分は、ChatGPTによって以下のようなコードに変換されます。

$$("#book-table tbody tr").first().$$("td").first().shouldHave(text("Java SEディープダイブ"));

テストシナリオに記述したボディ内の1行目の1列目といった日本語が、正しくDOMに変換されていることが分かります。これは「生成AIならでは」であり、ルールベースのテストツールでは、なかなかこうはいかないでしょう。

このように「テスト仕様をテキストで記述しやすい」という特徴から、UIテストにおける生成AIの活用は極めて効果的です。少なくとも今回のサンプルに限っては、Selenideのコードを直接記述するよりも、生成AIの活用に優位性があると言えます。上記の「テストシナリオ」は一つの例に過ぎませんが、ぜひプロンプトを工夫することで、UIテストの効率化を図ってみてはいかがでしょうか。

5. Gatlingによる負荷テストでの活用

この項では、ChatGPTを活用して負荷テストコードを生成する事例を紹介します。

負荷テストの題材は、前項と同様に「テックブックストア」です。生成するテストコードは、Gatlingによるものとします。負荷テストシナリオは前項と同様であり、書籍の8.1.3項のBookStoreSimulationクラスが一種の模範解答になります。

ここでも既出のアプローチ①に則り、まず「負荷テストシナリオ」を作成し、それをインプットにしてChatGPTに負荷テストコードを生成させます。具体的には、以下のようなプロンプトを投入します。

以下の[負荷テストシナリオ]および[シミュレーションの全体設定]に従い、負荷テストを行います。
Gatlingのシミュレーションクラスを、以下の[制約条件]に基づき、概念的なJavaコードとして生成してください。

# 制約条件

* パッケージ名は`pro.kensait.gatling.bookstore.scenario1`、クラス名は`BookStoreSimulation`とします。
* シミュレーションクラスは、`io.gatling.javaapi.core.Simulation`を継承してください。
* ベースURLは、`localhost:8080`に設定してください。
* シナリオは、`ScenarioBuilder`を使って生成してください。
* フィーダーは、CSVファイルを"data/users.csv"から取得し、サイクリックに読み込んでくださいください。
  また読み込んだデータは、userId、passwordという名前でセッション変数に保存するものとします。
* `setUp()`には、イニシャライザーを使ってください。
* Titleの検証は、`css("title")`と指定することでTitleを取得してください。
* "Save CSRF"では、`csrfToken`というIDのvalue属性からCSRFトークンを取得し、`sessionCsrfToken`という名前のセッション変数で保持してください。
* シナリオは、`pace()`メソッドによって、シナリオ番号1~12の間を、30秒間のペースに調整してください。
* シナリオは、`forever()`メソッドによって、無限に繰り返してください。
* 個々のアクション間には、2秒の休止時間を入れてください。

# 負荷テストシナリオ

|番号|アクション|メソッド|URL|パラメータ|検証|
|:--|:--|:--|:--|:--|:--|
|1|Open|GET|/||Status: 200, Title: "TopPage", Save CSRF|
|2|Login|POST|/processLogin|email: "#{userId}", password: "#{password}", _csrf: #{sessionCsrfToken}|Status: 200, Title: "BookSelectPage", Save CSRF|
|3|Add Book|POST|/addBook|bookId: "2", _csrf: #{sessionCsrfToken}|Status: 200, Title: "CartViewPage", Save CSRF|
....中略....
|11|Fix|POST|/fix|_csrf: #{sessionCsrfToken}|Status: 200, Title: "BookOrderPage", Save CSRF|
|12|Order|POST|/order1|settlementType: "1", _csrf: #{sessionCsrfToken}|Status: 200, Title: "OrderSuccessPage", Save CSRF|
|13|Logout|POST|/processLogout|_csrf: #{sessionCsrfToken}|Status: 200, Title: "FinishPage"|

# シミュレーションの全体設定

* 最初は1ユーザから始め、100秒かけて最大5ユーザーまで増加させてください。
* 起動してから200秒経過したら、シミュレーション全体を終了してください。

「負荷テストシナリオ」の内容は、既出のものと同様です。ここではExcel等の表計算ソフトで作成し、それをプロンプトに貼り付けやすくするためにMarkdown形式に変換しています。

図5 Excelで作成した負荷テストシナリオ
図5
※クリックして拡大して表示できます。

このようなプロンプトを投入すると、Gatlingによる負荷テストコードが生成されます。その内容はここでは割愛しますが、模範解答であるBookStoreSimulationクラスとほぼ同様のコードであり、仕様通りにそのまま動作します。

一点注意が必要なのは、Gatlingは基本的にScalaベースの負荷テストツールのため、テストコードをJavaで記述できるということを、本稿執筆時点で"gpt4"が理解していなかったという点です。

したがってプロンプトを工夫し、⁠概念的なJavaコードとして生成してください」と指示していますが、結果的には問題のないJavaコードが生成されています。この点は、Javaベースの事例の増加とモデルのアップデートによって、将来的には解消することが予想されます。

このように「テスト仕様をテキストで記述しやすい」という特徴から、UIテストと同様に、負荷テストでも生成AIは極めて有効です。生成AIを活用すると、負荷テストを効率的に実施することが可能になるでしょう。

おすすめ記事

記事・ニュース一覧