キュウリを使ったREST APIテスト

1概要

このチュートリアルでは、https://cucumber.io/[Cucumber]、ユーザー受け入れテストに一般的に使用されるツール、およびREST APIテストでの使用方法について紹介します。

さらに、記事を自己完結型にし、外部のRESTサービスから独立させるために、スタブ付けおよびモックのWebサービスライブラリであるWireMockを使用します。このライブラリについてもっと知りたい場合は、リンク:/Introduction-to-wiremock[Introduction to WireMock]を参照してください。

2ガーキン - キュウリの言語

キュウリは、動作駆動開発(BDD)をサポートするテストフレームワークで、ユーザーはプレーンテキストでアプリケーション操作を定義できます。これはhttps://github.com/cucumber/cucumber/wiki/Gherkin[Gerkin Domain Specific Language](DSL)に基づいて動作します。 Gherkinのこのシンプルだが強力な構文により、開発者とテスターは複雑なテストを書くことができますが、技術的でないユーザーでも理解しやすくなります。

2.1. ガーキンの紹介

Gherkinは、行末、インデント、およびキーワードを使用して文書を定義する行指向言語です。それぞれの非空白行は通常Gherkinキーワードで始まり、その後に任意のテキストが続きます。これは通常キーワードの説明です。

Cucumberが認識できるように、構造全体を feature 拡張子を付けてファイルに書き込む必要があります。

これが簡単なGherkin文書の例です。

Feature: A short description of the desired functionality

  Scenario: A business situation
    Given a precondition
    And another precondition
    When an event happens
    And another event happens too
    Then a testable outcome is achieved
    And something else is also completed

以下のサブセクションでは、ガーキン構造の中で最も重要な要素をいくつか説明します。

2.2. 特徴

Gherkinファイルは、テストが必要なアプリケーション機能を記述するために使用されます。ファイルの最初の部分には Feature というキーワードがあり、その後に同じ行に機能名が続き、その下に複数の行にまたがることがあるオプションの説明が続きます。

Feature キーワードを除くすべてのテキストは、Cucumberパーサーによってスキップされ、資料の目的でのみ含まれています。

2.3. シナリオとステップ

ガーキン構造は、 Scenario キーワードによって認識される1つ以上のシナリオで構成されることがあります。シナリオは基本的にユーザーがアプリケーションの機能を検証することを可能にするテストです。それは初期の文脈、起こり得る出来事、そしてそれらの出来事によって生み出された期待される結果を記述するべきです。

これらのことは、 Given When Then And 、および But の5つのキーワードのいずれかによって識別されるステップを使用して行われます。

  • Given :このステップはシステムを明確に定義された状態にすることです

ユーザーがアプリケーションと対話する前に。 Given 句は、ユースケースの前提条件と見なすことができます。

  • When : When ステップは、に発生するイベントを説明するために使用されます。

応用。これは、ユーザーが実行したアクション、または別のシステムによって引き起こされたイベントです。

  • Then :このステップはテストの予想結果を指定することです。の

結果は、テスト対象の機能のビジネス価値に関連しているはずです。

  • And および But :これらのキーワードは上記のステップを置き換えるのに使用することができます

同じ種類のステップが複数ある場合のキーワード。

キュウリは実際にこれらのキーワードを区別しません、しかし、それらは機能をより読みやすくしてBDD構造と一致させるためにまだあります。

3キュウリ-JVM実装

CucumberはもともとRubyで書かれていて、Cucumber-JVM実装でJavaに移植されています。

3.1. Mavenの依存関係

MavenプロジェクトでCucumber-JVMを使用するには、POMに次の依存関係を含める必要があります。

<dependency>
    <groupId>info.cukes</groupId>
    <artifactId>cucumber-java</artifactId>
    <version>1.2.4</version>
    <scope>test</scope>
</dependency>

Cucumberを使ったJUnitテストを容易にするためには、もう1つの依存関係が必要です。

<dependency>
    <groupId>info.cukes</groupId>
    <artifactId>cucumber-junit</artifactId>
    <version>1.2.4</version>
</dependency>

あるいは、Java 8のラムダ式を利用するために別のアーティファクトを使用することもできます。このチュートリアルでは説明しません。

** 3.2. ステップの定義

Gherkinのシナリオが行動に変換されなければ意味がありません。これがステップの定義が役立つところです。基本的に、ステップ定義は、プレーンテキストのGherkinステップを実行可能コードに変換することを目的とした、添付パターンを持つ注釈付きJavaメソッドです。

フィーチャー文書を解析した後、Cucumberは実行するために事前定義されたGherkinステップと一致するステップ定義を探します。

わかりやすくするために、次の手順を見てみましょう。

Given I have registered a course in Baeldung

そしてステップの定義:

@Given("I have registered a course in Baeldung")
public void verifyAccount() {
   //method implementation
}

Cucumberが与えられたステップを読むとき、それは注釈パターンがGherkinテキストと一致するステップ定義を探します。この図では、 testMethod メソッドが一致していることがわかり、そのコードが実行されて、 Let me in! 文字列がコンソールに出力されます。

4テストの作成と実行

4.1. 機能ファイルの書き方

.feature 拡張子で終わる名前のファイルでシナリオとステップを宣言することから始めましょう:

Feature: Testing a REST API
  Users should be able to submit GET and POST requests to a web service,
  represented by WireMock

  Scenario: Data Upload to a web service
    When users upload data on a project
    Then the server should handle it and return a success status

  Scenario: Data retrieval from a web service
    When users want to get information on the Cucumber project
    Then the requested data is returned

実行時にディレクトリがクラスパスにロードされるという条件で、このファイルを Feature という名前のディレクトリに保存します。

src/main/resources

4.2. Cucumber で動作するようにJUnitを設定する

実行時にJUnitがCucumberを認識して機能ファイルを読み取るには、 Cucumber クラスを Runner として宣言する必要があります。また、機能ファイルとステップ定義を検索する場所をJUnitに指示する必要があります。

@RunWith(Cucumber.class)
@CucumberOptions(features = "classpath:Feature")
public class CucumberTest {

}

ご覧のとおり、 CucumberOption features 要素は、以前に作成された機能ファイルを見つけます。 glue というもう1つの重要な要素は、ステップ定義へのパスを提供します。ただし、テストケースとステップの定義がこのチュートリアルと同じパッケージ内にある場合は、その要素は削除される可能性があります。

4.3. ステップ定義の書き方

Cucumberがステップを解析するとき、Gherkinキーワードでアノテートされたメソッドを検索して、一致するステップ定義を見つけます。このチュートリアルでは、これらのステップ定義は CucumberTest を使用して同じパッケージ内のクラスで定義されています。

以下は、Gherkinステップに完全に一致する方法です。このメソッドはREST Webサービスにデータをポストするために使用されます。

@When("^users upload data on a project$")
public void usersUploadDataOnAProject() throws IOException {

}

そして、これがGherkinステップに一致するメソッドで、テキストから引数を取ります。これはREST Webサービスから情報を取得するために使用されます。

@When("^users want to get information on the (.+) project$")
public void usersGetInformationOnAProject(String projectName) throws IOException {

}

ご覧のとおり、 usersGetInformationOnAProject メソッドは String 引数を取ります。これはプロジェクト名です。この引数は注釈内で (。) によって宣言されており、ここではステップテキスト内の Cucumber に対応しています。

上記の両方のメソッドの作業コードは次のセクションで提供されます。

4.4. テストの作成と実行

まず、POSTリクエストによってサーバーにアップロードされ、GETを使用してクライアントにダウンロードされたデータを示すためのJSON構造から始めます。この構造は jsonString フィールドに保存され、以下に示されています。

{
    "testing-framework": "cucumber",
    "supported-language":
   [        "Ruby",
        "Java",
        "Javascript",
        "PHP",
        "Python",
        "C++"
   ],

    "website": "cucumber.io"
}

REST APIを実演するために、WireMockサーバーが登場します。

WireMockServer wireMockServer = new WireMockServer(options().dynamicPort());

さらに、このチュートリアルではApache HttpClient APIを使用してサーバーへの接続に使用されるクライアントを表します。

CloseableHttpClient httpClient = HttpClients.createDefault();

それでは、ステップ定義内にテストコードを書くことに移りましょう。最初に usersUploadDataOnAProject メソッドに対してこれを行います。

クライアントが接続する前にサーバーが稼働している必要があります。

wireMockServer.start();

WireMock APIを使用してRESTサービスをスタブ化します。

configureFor("localhost", wireMockServer.port());
stubFor(post(urlEqualTo("/create"))
  .withHeader("content-type", equalTo("application/json"))
  .withRequestBody(containing("testing-framework"))
  .willReturn(aResponse().withStatus(200)));

それでは、上記で宣言した jsonString フィールドから取得した内容を含むPOSTリクエストをサーバーに送信します。

HttpPost request = new HttpPost("http://localhost:" + wireMockServer.port() + "/create");
StringEntity entity = new StringEntity(jsonString);
request.addHeader("content-type", "application/json");
request.setEntity(entity);
HttpResponse response = httpClient.execute(request);

次のコードは、POSTリクエストが正常に受信され処理されたことを表明します。

assertEquals(200, response.getStatusLine().getStatusCode());
verify(postRequestedFor(urlEqualTo("/create"))
  .withHeader("content-type", equalTo("application/json")));

サーバーは使用後に停止する必要があります。

wireMockServer.stop();

ここで実装する2番目のメソッドは usersGetInformationOnAProject(String) です。最初のテストと同様に、サーバーを起動してRESTサービスをスタブする必要があります。

wireMockServer.start();

configureFor("localhost", wireMockServer.port());
stubFor(get(urlEqualTo("/projects/cucumber"))
  .withHeader("accept", equalTo("application/json"))
  .willReturn(aResponse().withBody(jsonString)));

GETリクエストを送信してレスポンスを受け取る:

HttpGet request = new HttpGet("http://localhost:" + wireMockServer.port() + "/projects/" + projectName.toLowerCase());
request.addHeader("accept", "application/json");
HttpResponse httpResponse = httpClient.execute(request);

ヘルパーメソッドを使用して、 httpResponse 変数を String に変換します。

String responseString = convertResponseToString(httpResponse);

その変換ヘルパーメソッドの実装は次のとおりです。

private String convertResponseToString(HttpResponse response) throws IOException {
    InputStream responseStream = response.getEntity().getContent();
    Scanner scanner = new Scanner(responseStream, "UTF-8");
    String responseString = scanner.useDelimiter("\\Z").next();
    scanner.close();
    return responseString;
}

以下はプロセス全体を検証します。

assertThat(responseString, containsString("\"testing-framework\": \"cucumber\""));
assertThat(responseString, containsString("\"website\": \"cucumber.io\""));
verify(getRequestedFor(urlEqualTo("/projects/cucumber"))
  .withHeader("accept", equalTo("application/json")));

最後に、前述のようにサーバーを停止します。

5並行して機能を実行する

時々、テストプロセスをスピードアップするために機能を並行して実行する必要があります。

  • cucumber-jvm-parallel-plugin を使用して、各機能/シナリオごとに個別のランナーを作成できます。その後、 mavenを構成します。結果のランナーを並行して実行するための-failsafe-plugin

まず、 pom.xmlに cucumber-jvm-parallel-plugin を追加する必要があります。

<plugin>
  <groupId>com.github.temyers</groupId>
  <artifactId>cucumber-jvm-parallel-plugin</artifactId>
  <version>5.0.0</version>
  <executions>
    <execution>
      <id>generateRunners</id>
      <phase>generate-test-sources</phase>
      <goals>
        <goal>generateRunners</goal>
      </goals>
      <configuration>
        <glue>
          <package>com.baeldung.rest.cucumber</package>
        </glue>
        <featuresDirectory>src/test/resources/Feature/</featuresDirectory>
        <parallelScheme>SCENARIO</parallelScheme>
      </configuration>
    </execution>
  </executions>
</plugin>

cucumber-jvm-parallel-plugin は複数のパラメータを持つので簡単にカスタマイズできます。これが私たちが使ったものです。

  • glue.package: (必須)統合テストパッケージ

  • featuresDirectory: ディレクトリへのパスは私たちの機能を含みます

ファイル ** parallelScheme: は、SCENARIOまたはFEATUREのいずれかになります。

シナリオごとに1つのランナーを生成し、機能ごとに1つのランナーを生成します

それでは、結果のランナーを並行して実行するように maven-failsafe-plugin を設定します。

<plugin>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.19.1</version>
    <configuration>
        <parallel>classes</parallel>
        <threadCount>2</threadCount>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>integration-test</goal>
                <goal>verify</goal>
            </goals>
        </execution>
    </executions>
</plugin>

ご了承ください:

  • parallel: class、methods 、またはbothにすることができます。

classes は各テストクラスを別々のスレッドで実行させます。 ** threadCount: は、これに割り当てるスレッド数を示します。

実行

それからテストを実行するために、我々はコマンドを使うことができます:

mvn verify

各シナリオは別々のスレッドで実行されることに気付くでしょう。

6. 結論

このチュートリアルでは、Cucumberの基本と、このフレームワークがREST APIをテストするためにGherkinドメイン固有の言語を使用する方法について説明しました。

これらすべての例とコードスニペットの実装はhttps://github.com/eugenp/tutorials/tree/master/testing-modules/rest-testing[GitHubプロジェクト]にあります。