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

Cucumberを使用したREST APIテスト

1. 概要

このチュートリアルでは、ユーザー受け入れテストで一般的に使用されるツールであるCucumberと、RESTAPIテストでの使用方法を紹介します。

さらに、記事を自己完結型で外部RESTサービスから独立させるために、スタブ作成およびモック作成のWebサービスライブラリであるWireMockを使用します。 このライブラリについて詳しく知りたい場合は、introduction to WireMockを参照してください。

2. ガーキン–キュウリの言語

Cucumberは、ビヘイビア駆動開発(BDD)をサポートするテストフレームワークであり、ユーザーはアプリケーション操作をプレーンテキストで定義できます。 Gherkin Domain Specific Language(DSL)に基づいて機能します。 このシンプルで強力なGherkinの構文により、開発者とテスターは複雑なテストを記述しながら、技術に詳しくないユーザーでも理解できるようになります。

2.1. ガーキン入門

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

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

簡単なガーキンドキュメントの例を次に示します。

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. シナリオと手順

Gherkin構造は、Scenarioキーワードによって認識される1つ以上のシナリオで構成されている場合があります。 シナリオは基本的に、ユーザーがアプリケーションの機能を検証できるようにするテストです。 初期コンテキスト、発生する可能性のあるイベント、およびそれらのイベントによって作成される予想される結果を記述する必要があります。

これらは、GivenWhenThenAnd、およびButの5つのキーワードのいずれかで識別されるステップを使用して実行されます。

  • Given:このステップは、ユーザーがアプリケーションとの対話を開始する前に、システムを明確に定義された状態にすることです。 Given句は、ユースケースの前提条件と見なすことができます。

  • WhenWhenステップは、アプリケーションで発生するイベントを記述するために使用されます。 これは、ユーザーが実行したアクション、または別のシステムによってトリガーされたイベントです。

  • Then:このステップは、テストの期待される結果を指定するためのものです。 結果は、テスト対象の機能のビジネス価値に関連する必要があります。

  • AndおよびBut:これらのキーワードは、同じタイプのステップが複数ある場合に、上記のステップキーワードを置き換えるために使用できます。

Cucumberは実際にはこれらのキーワードを区別しませんが、機能を読みやすく、BDD構造と一貫性を保つためにまだ存在しています。

3. キュウリ-JVMの実装

CucumberはもともとRubyで書かれており、このセクションの主題であるCucumber-JVM実装でJavaに移植されています。

3.1. Mavenの依存関係

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


    info.cukes
    cucumber-java
    1.2.4
    test

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


    info.cukes
    cucumber-junit
    1.2.4

別の方法として、別のアーティファクトを使用して、Java 8のラムダ式を利用することもできますが、このチュートリアルでは取り上げません。

3.2. ステップ定義

Gherkinシナリオは、アクションに変換されなければ役に立たないでしょう。ここで、ステップ定義が役立ちます。 基本的に、ステップ定義は、プレーンテキストのGherkinステップを実行可能コードに変換することを目的とした、パターンが付加された注釈付きJavaメソッドです。 機能ドキュメントを解析した後、Cucumberは実行する定義済みのGherkinステップに一致するステップ定義を検索します。

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

Given I have registered a course in example

そしてステップ定義:

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

Cucumberが指定されたステップを読み取るとき、注釈パターンがガーキンテキストと一致するステップ定義を探します。 この図では、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 {

}

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

4.3. ステップ定義の記述

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

以下は、ガーキンステップに完全に一致する方法です。 このメソッドは、データを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 projectName)です。 最初のテストと同様に、サーバーを起動してから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. 機能を並行して実行する

場合によっては、テストプロセスを高速化するために機能を並行して実行する必要があります。

We can use the cucumber-jvm-parallel-plugin to create a separate runner for each feature/scenario.次に、結果のランナーを並行して実行するようにmaven-failsafe-pluginを構成します。

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


  com.github.temyers
  cucumber-jvm-parallel-plugin
  5.0.0
  
    
      generateRunners
      generate-test-sources
      
        generateRunners
      
      
        
          com.example.rest.cucumber
        
        src/test/resources/Feature/
        SCENARIO
      
    
  

複数のパラメータがあるため、cucumber-jvm-parallel-pluginを簡単にカスタマイズできます。 使用したものは次のとおりです。

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

  • featuresDirectory:ディレクトリへのパスには機能ファイルが含まれています

  • parallelScheme:は、SCENARIOまたはFEATUREのいずれかで、SCENARIOはシナリオごとに1つのランナーを生成し、FEATUREは機能ごとに1つのランナーを生成します。

ここで、configure the maven-failsafe-plugin to execute resulting runners in parallelも行います。


    maven-failsafe-plugin
    2.19.1
    
        classes
        2
    
    
        
            
                integration-test
                verify
            
        
    

ご了承ください:

  • parallel:classes, methodsまたはその両方にすることができます-この場合、classesは各テストクラスを別々のスレッドで実行します

  • threadCount:は、この実行に割り当てるスレッドの数を示します

次に、テストを実行するために、コマンドを使用できます。

mvn verify

各シナリオが別々のスレッドで実行されていることがわかります。

6. 結論

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

これらすべての例とコードスニペットの実装は、a GitHub projectにあります。