1.はじめに

この記事では、 JBehave について簡単に説明し、次にBDDの観点からRESTAPIのテストに焦点を当てます。

2. JBehaveとBDD

JBehaveは、ビヘイビア駆動開発フレームワークです。 これは、自動化された受け入れテストのための直感的でアクセス可能な方法を提供することを目的としています。

BDDに慣れていない場合は、この記事から始めて、別のBDDテストフレームワークであるCucumber について説明し、一般的なBDDの構造と機能を紹介することをお勧めします。

他のBDDフレームワークと同様に、JBehaveは次の概念を採用しています。

  • ストーリー–自動的に実行可能なビジネス機能の増分を表し、1つ以上のシナリオで構成されます
  • シナリオ–システムの動作の具体例を表します
  • 手順–従来のBDDキーワードを使用して実際の動作を表します: Given When Then

典型的なシナリオは次のとおりです。

Given a precondition
When an event occurs
Then the outcome should be captured

シナリオの各ステップは、JBehaveのアノテーションに対応しています。

  • @Given :コンテキストを開始します
  • @When :アクションを実行します
  • @Then :期待される結果をテストします

3. Mavenの依存関係

MavenプロジェクトでJBehaveを利用するには、jbehave-core依存関係をpomに含める必要があります。

<dependency>
    <groupId>org.jbehave</groupId>
    <artifactId>jbehave-core</artifactId>
    <version>4.1</version>
    <scope>test</scope>
</dependency>

4. 簡単な例

JBehaveを使用するには、次の手順に従う必要があります。

  1. ユーザーストーリーを書く
  2. ユーザーストーリーからJavaコードへのステップのマッピング
  3. ユーザーストーリーを構成する
  4. JBehaveテストを実行する
  5. 結果を確認する

4.1. 話

次の簡単な話から始めましょう:「ユーザーとして、カウンターの値を1増やすことができるように、カウンターを増やしたい」。

.storyファイルでストーリーを定義できます。

Scenario: when a user increases a counter, its value is increased by 1

Given a counter
And the counter has any integral value
When the user increases the counter
Then the value of the counter must be 1 greater than previous value

4.2. マッピング手順

手順を踏まえて、これをJavaに実装しましょう。

public class IncreaseSteps {
    private int counter;
    private int previousValue;

    @Given("a counter")
    public void aCounter() {
    }

    @Given("the counter has any integral value")
    public void counterHasAnyIntegralValue() {
        counter = new Random().nextInt();
        previousValue = counter;
    }

    @When("the user increases the counter")
    public void increasesTheCounter() {
        counter++;
    }

    @Then("the value of the counter must be 1 greater than previous value")
    public void theValueOfTheCounterMustBe1Greater() {
        assertTrue(1 == counter - previousValue);
    }
}

注釈の値は、説明と正確に一致する必要があることに注意してください。

4.3. ストーリーの構成

手順を実行するには、ストーリーのステージを設定する必要があります。

public class IncreaseStoryLiveTest extends JUnitStories {

    @Override
    public Configuration configuration() {
        return new MostUsefulConfiguration()
          .useStoryLoader(new LoadFromClasspath(this.getClass()))
          .useStoryReporterBuilder(new StoryReporterBuilder()
            .withCodeLocation(codeLocationFromClass(this.getClass()))
            .withFormats(CONSOLE));
    }

    @Override
    public InjectableStepsFactory stepsFactory() {
        return new InstanceStepsFactory(configuration(), new IncreaseSteps());
    }

    @Override
    protected List<String> storyPaths() {
        return Arrays.asList("increase.story");
    }

}

storyPaths()では、JBehaveによって解析される.storyファイルパスを提供します。 実際のステップの実装はstepsFactory()で提供されます。 次に、 configuration()で、ストーリーローダーとストーリーレポートが適切に構成されます。

すべての準備が整ったので、 mvn cleantestを実行するだけでストーリーを開始できます。

4.4. テスト結果の確認

テスト結果はコンソールで確認できます。 テストに合格したため、出力はストーリーと同じになります。

Scenario: when a user increases a counter, its value is increased by 1
Given a counter
And the counter has any integral value
When the user increases the counter
Then the value of the counter must be 1 greater than previous value

シナリオのいずれかのステップを実装するのを忘れた場合は、レポートで通知されます。 @Whenステップを実装しなかったとしましょう。

Scenario: when a user increases a counter, its value is increased by 1
Given a counter
And the counter has any integral value
When the user increases the counter (PENDING)
Then the value of the counter must be 1 greater than previous value (NOT PERFORMED)
@When("the user increases the counter")
@Pending
public void whenTheUserIncreasesTheCounter() {
    // PENDING
}

レポートには、 @When ステップが保留中であると記載されているため、@Thenステップは実行されません。

@Thenステップが失敗した場合はどうなりますか? レポートからすぐにエラーを見つけることができます。

Scenario: when a user increases a counter, its value is increased by 1
Given a counter
And the counter has any integral value
When the user increases the counter
Then the value of the counter must be 1 greater than previous value (FAILED)
(java.lang.AssertionError)

5. RESTAPIのテスト

これで、JBhaveの基本を理解しました。 RESTAPIを使用してテストする方法を説明します。 テストは、Javaを使用してRESTAPIをテストする方法について説明した以前の記事に基づいています。

その記事では、 GitHub REST API をテストし、主にHTTP応答コード、ヘッダー、ペイロードに焦点を当てました。 簡単にするために、それらをそれぞれ3つの別々のストーリーに書き込むことができます。

5.1. ステータスコードのテスト

物語:

Scenario: when a user checks a non-existent user on github, github would respond 'not found'

Given github user profile api
And a random non-existent username
When I look for the random user via the api
Then github respond: 404 not found

When I look for eugenp1 via the api
Then github respond: 404 not found

When I look for eugenp2 via the api
Then github respond: 404 not found

手順:

public class GithubUserNotFoundSteps {

    private String api;
    private String nonExistentUser;
    private int githubResponseCode;

    @Given("github user profile api")
    public void givenGithubUserProfileApi() {
        api = "https://api.github.com/users/%s";
    }

    @Given("a random non-existent username")
    public void givenANonexistentUsername() {
        nonExistentUser = randomAlphabetic(8);
    }

    @When("I look for the random user via the api")
    public void whenILookForTheUserViaTheApi() throws IOException {
        githubResponseCode = getGithubUserProfile(api, nonExistentUser)
          .getStatusLine()
          .getStatusCode();
    }

    @When("I look for $user via the api")
    public void whenILookForSomeNonExistentUserViaTheApi(
      String user) throws IOException {
        githubResponseCode = getGithubUserProfile(api, user)
          .getStatusLine()
          .getStatusCode();
    }

    @Then("github respond: 404 not found")
    public void thenGithubRespond404NotFound() {
        assertTrue(SC_NOT_FOUND == githubResponseCode);
    }

    //...
}

ステップの実装で、パラメータインジェクション機能をどのように使用したかに注目してください。 ステップ候補から抽出された引数は、自然な順序に従って、注釈付きJavaメソッドのパラメーターと一致します。

また、注釈付きの名前付きパラメーターがサポートされています。

@When("I look for $username via the api")
public void whenILookForSomeNonExistentUserViaTheApi(
  @Named("username") String user) throws IOException

5.2. メディアタイプのテスト

簡単なMIMEタイプのテストストーリーは次のとおりです。

Scenario: when a user checks a valid user's profile on github, github would respond json data

Given github user profile api
And a valid username
When I look for the user via the api
Then github respond data of type json

そして、ここにステップがあります:

public class GithubUserResponseMediaTypeSteps {

    private String api;
    private String validUser;
    private String mediaType;

    @Given("github user profile api")
    public void givenGithubUserProfileApi() {
        api = "https://api.github.com/users/%s";
    }

    @Given("a valid username")
    public void givenAValidUsername() {
        validUser = "eugenp";
    }

    @When("I look for the user via the api")
    public void whenILookForTheUserViaTheApi() throws IOException {
        mediaType = ContentType
          .getOrDefault(getGithubUserProfile(api, validUser).getEntity())
          .getMimeType();
    }

    @Then("github respond data of type json")
    public void thenGithubRespondDataOfTypeJson() {
        assertEquals("application/json", mediaType);
    }
}

5.3. JSONペイロードのテスト

そして最後の話:

Scenario: when a user checks a valid user's profile on github, github's response json should include a login payload with the same username

Given github user profile api
When I look for eugenp via the api
Then github's response contains a 'login' payload same as eugenp

そして、単純なストレートステップの実装:

public class GithubUserResponsePayloadSteps {

    private String api;
    private GitHubUser resource;

    @Given("github user profile api")
    public void givenGithubUserProfileApi() {
        api = "https://api.github.com/users/%s";
    }

    @When("I look for $user via the api")
    public void whenILookForEugenpViaTheApi(String user) throws IOException {
        HttpResponse httpResponse = getGithubUserProfile(api, user);
        resource = RetrieveUtil.retrieveResourceFromResponse(httpResponse, GitHubUser.class);
    }

    @Then("github's response contains a 'login' payload same as $username")
    public void thenGithubsResponseContainsAloginPayloadSameAsEugenp(String username) {
        assertThat(username, Matchers.is(resource.getLogin()));
    }
}

6. 概要

この記事では、JBehaveを簡単に紹介し、BDDスタイルのRESTAPIテストを実装しました。

プレーンなJavaテストコードと比較すると、JBehaveで実装されたコードは非常に明確で直感的に見え、テスト結果レポートははるかにエレガントに見えます。

いつものように、サンプルコードはGithubプロジェクトにあります。