1前書き

以前は、/serenity-bddというリンクがありました[Serenity BDDフレームワークの紹介]。

この記事では、Serenity BDDとSpringを統合する方法を紹介します。


2 Mavenの依存関係

SpringプロジェクトでSerenityを有効にするには、


serenity-core




httpsを追加する必要があります。//search.maven.org/classic/#artifactdetails%7Cnet.serenity-bdd%7Cserenity-spring%7C1.4.0%7Cjar[serenity-spring]

から

pom.xml

へ:

<dependency>
    <groupId>net.serenity-bdd</groupId>
    <artifactId>serenity-core</artifactId>
    <version>1.4.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>net.serenity-bdd</groupId>
    <artifactId>serenity-spring</artifactId>
    <version>1.4.0</version>
    <scope>test</scope>
</dependency>



serenity-maven-plugin


も設定する必要がありますセレニティテストレポートを生成するために重要です。

<plugin>
    <groupId>net.serenity-bdd.maven.plugins</groupId>
    <artifactId>serenity-maven-plugin</artifactId>
    <version>1.4.0</version>
    <executions>
        <execution>
            <id>serenity-reports</id>
            <phase>post-integration-test</phase>
            <goals>
                <goal>aggregate</goal>
            </goals>
        </execution>
    </executions>
</plugin>


3春の統合

Spring統合テストは

@ RunWith



SpringJUnit4ClassRunner


が必要です。

ただし、Serenityテストは

SerenityRunner

によって実行される必要があるため、Serenityでテストランナーを直接使用することはできません。

Serenityを使ったテストでは、注入を有効にするために

SpringIntegrationMethodRule



SpringIntegrationClassRule

を使うことができます。

簡単なシナリオに基づいてテストを行います。数値を指定すると、別の数値を追加するときに合計を返します。


3.1.

SpringIntegrationMethodRule



SpringIntegrationMethodRule

はテストメソッドに適用されるhttp://junit.org/junit4/javadoc/4.12/org/junit/rules/MethodRule.html[

MethodRule

]です。 Springコンテキストは

@ Before

の前と

@ BeforeClass

の後に作成されます。

Beanにインジェクトするプロパティがあるとします。

……
<util:properties id = “props”>
    <prop key = “adder”> 4 </prop>
</util:properties>
……

それでは、テストで値の注入を有効にするために

SpringIntegrationMethodRule

を追加しましょう。

@RunWith(SerenityRunner.class)
@ContextConfiguration(locations = "classpath:adder-beans.xml")
public class AdderMethodRuleIntegrationTest {

    @Rule
    public SpringIntegrationMethodRule springMethodIntegration
      = new SpringIntegrationMethodRule();

    @Steps
    private AdderSteps adderSteps;

    @Value("#{props['adder']}")
    private int adder;

    @Test
    public void givenNumber__whenAdd__thenSummedUp() {
        adderSteps.givenNumber();
        adderSteps.whenAdd(adder);
        adderSteps.thenSummedUp();
    }
}

それはまた

spring test

のメソッドレベルアノテーションもサポートします。あるテストメソッドがテストコンテキストを汚している場合は、http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/test/annotation/DirtiesContext.html[

@DirtiesContext

]とマークすることができます:

@RunWith(SerenityRunner.class)
@FixMethodOrder(MethodSorters.NAME__ASCENDING)
@ContextConfiguration(classes = AdderService.class)
public class AdderMethodDirtiesContextIntegrationTest {

    @Steps private AdderServiceSteps adderServiceSteps;

    @Rule public SpringIntegrationMethodRule springIntegration = new SpringIntegrationMethodRule();

    @DirtiesContext
    @Test
    public void __0__givenNumber__whenAddAndAccumulate__thenSummedUp() {
        adderServiceSteps.givenBaseAndAdder(randomInt(), randomInt());
        adderServiceSteps.whenAccumulate();
        adderServiceSteps.summedUp();

        adderServiceSteps.whenAdd();
        adderServiceSteps.sumWrong();
    }

    @Test
    public void __1__givenNumber__whenAdd__thenSumWrong() {
        adderServiceSteps.whenAdd();
        adderServiceSteps.sumWrong();
    }

}

上記の例では、

adderServiceSteps.whenAccumulate()

を呼び出すと、

adderServiceSteps

に挿入された

@​​ Service

の基数フィールドが変更されます。

@ContextConfiguration(classes = AdderService.class)
public class AdderServiceSteps {

    @Autowired
    private AdderService adderService;

    private int givenNumber;
    private int base;
    private int sum;

    public void givenBaseAndAdder(int base, int adder) {
        this.base = base;
        adderService.baseNum(base);
        this.givenNumber = adder;
    }

    public void whenAdd() {
        sum = adderService.add(givenNumber);
    }

    public void summedUp() {
        assertEquals(base + givenNumber, sum);
    }

    public void sumWrong() {
        assertNotEquals(base + givenNumber, sum);
    }

    public void whenAccumulate() {
        sum = adderService.accumulate(givenNumber);
    }

}

具体的には、合計を基数に割り当てます。

@Service
public class AdderService {

    private int num;

    public void baseNum(int base) {
        this.num = base;
    }

    public int currentBase() {
        return num;
    }

    public int add(int adder) {
        return this.num + adder;
    }

    public int accumulate(int adder) {
        return this.num += adder;
    }
}

最初のテスト

__0

givenNumber

whenAddAndAccumulate

thenSummedUp__では、ベース番号が変更され、コンテキストがダーティになります。別の数を追加しようとすると、予想される合計が得られません。

最初のテストを

@ DirtiesContext

でマークしたとしても、2番目のテストは影響を受けます。追加した後も、合計はまだ間違っています。

どうして?

現在、メソッドレベル

@ DirtiesContext

を処理している間、SerenityのSpring統合は現在のテストインスタンスのテストコンテキストを再構築するだけです。


@ Steps

内の基礎となる依存関係コンテキストは再構築されません。

この問題を回避するには、現在のテストインスタンスに

@ Service

を挿入し、

@ Steps

の明示的な依存関係としてserviceを作成します。

@RunWith(SerenityRunner.class)
@FixMethodOrder(MethodSorters.NAME__ASCENDING)
@ContextConfiguration(classes = AdderService.class)
public class AdderMethodDirtiesContextDependencyWorkaroundIntegrationTest {

    private AdderConstructorDependencySteps adderSteps;

    @Autowired private AdderService adderService;

    @Before
    public void init() {
        adderSteps = new AdderConstructorDependencySteps(adderService);
    }

   //...
}

public class AdderConstructorDependencySteps {

    private AdderService adderService;

    public AdderConstructorDependencySteps(AdderService adderService) {
        this.adderService = adderService;
    }

   //...
}

あるいは、汚い文脈を避けるために

@ Before

セクションに条件初期化ステップを入れることができます。しかし、この種の解決策はいくつかの複雑な状況では利用できないかもしれません。

@RunWith(SerenityRunner.class)
@FixMethodOrder(MethodSorters.NAME__ASCENDING)
@ContextConfiguration(classes = AdderService.class)
public class AdderMethodDirtiesContextInitWorkaroundIntegrationTest {

    @Steps private AdderServiceSteps adderServiceSteps;

    @Before
    public void init() {
        adderServiceSteps.givenBaseAndAdder(randomInt(), randomInt());
    }

   //...
}


3.2.

SpringIntegrationClassRule


クラスレベルの注釈を有効にするには、

SpringIntegrationClassRule

を使用する必要があります。次のようなテストクラスがあるとしましょう。それぞれが文脈を汚します:

@RunWith(SerenityRunner.class)
@ContextConfiguration(classes = AdderService.class)
public static abstract class Base {

    @Steps AdderServiceSteps adderServiceSteps;

    @ClassRule public static SpringIntegrationClassRule springIntegrationClassRule = new SpringIntegrationClassRule();

    void whenAccumulate__thenSummedUp() {
        adderServiceSteps.whenAccumulate();
        adderServiceSteps.summedUp();
    }

    void whenAdd__thenSumWrong() {
        adderServiceSteps.whenAdd();
        adderServiceSteps.sumWrong();
    }

    void whenAdd__thenSummedUp() {
        adderServiceSteps.whenAdd();
        adderServiceSteps.summedUp();
    }
}

@DirtiesContext(classMode = AFTER__CLASS)
public static class DirtiesContextIntegrationTest extends Base {

    @Test
    public void givenNumber__whenAdd__thenSumWrong() {
        super.whenAdd__thenSummedUp();
        adderServiceSteps.givenBaseAndAdder(randomInt(), randomInt());
        super.whenAccumulate__thenSummedUp();
        super.whenAdd__thenSumWrong();
    }
}

@DirtiesContext(classMode = AFTER__CLASS)
public static class AnotherDirtiesContextIntegrationTest extends Base {

    @Test
    public void givenNumber__whenAdd__thenSumWrong() {
        super.whenAdd__thenSummedUp();
        adderServiceSteps.givenBaseAndAdder(randomInt(), randomInt());
        super.whenAccumulate__thenSummedUp();
        super.whenAdd__thenSumWrong();
    }
}

この例では、暗黙的な注入はすべてクラスレベル

@ DirtiesContext

に対して再構築されます。


3.3.

SpringIntegrationSerenityRunner


上記の両方の統合規則を自動的に追加する便利なクラス

SpringIntegrationSerenityRunner

があります。このランナーで上記のテストを実行して、テストでメソッドまたはクラスのテストルールを指定しないようにすることができます。

@RunWith(SpringIntegrationSerenityRunner.class)
@ContextConfiguration(locations = "classpath:adder-beans.xml")
public class AdderSpringSerenityRunnerIntegrationTest {

    @Steps private AdderSteps adderSteps;

    @Value("#{props['adder']}") private int adder;

    @Test
    public void givenNumber__whenAdd__thenSummedUp() {
        adderSteps.givenNumber();
        adderSteps.whenAdd(adder);
        adderSteps.thenSummedUp();
    }
}


4 SpringMVCの統合

SpringMVCコンポーネントをSerenityでテストするだけでよい場合は、

rest-assured



serenity-spring

統合の代わりに

RestAssuredMockMvc

を使用することができます。


4.1. Mavenの依存関係


rest-assured spring-mock-mvc

依存関係をに追加する必要があります。

pom.xml

:

<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>spring-mock-mvc</artifactId>
    <version>3.0.3</version>
    <scope>test</scope>
</dependency>


4.2. アクション中の

RestAssuredMockMvc


次のコントローラをテストしましょう。

@RequestMapping(value = "/adder", produces = MediaType.APPLICATION__JSON__UTF8__VALUE)
@RestController
public class PlainAdderController {

    private final int currentNumber = RandomUtils.nextInt();

    @GetMapping("/current")
    public int currentNum() {
        return currentNumber;
    }

    @PostMapping
    public int add(@RequestParam int num) {
        return currentNumber + num;
    }
}

このような

RestAssuredMockMvc

のMVCモックユーティリティを利用することができます。

@RunWith(SerenityRunner.class)
public class AdderMockMvcIntegrationTest {

    @Before
    public void init() {
        RestAssuredMockMvc.standaloneSetup(new PlainAdderController());
    }

    @Steps AdderRestSteps steps;

    @Test
    public void givenNumber__whenAdd__thenSummedUp() throws Exception {
        steps.givenCurrentNumber();
        steps.whenAddNumber(randomInt());
        steps.thenSummedUp();
    }
}

それから残りの部分は私達が

rest-assured

を使う方法と変わらない:

public class AdderRestSteps {

    private MockMvcResponse mockMvcResponse;
    private int currentNum;

    @Step("get the current number")
    public void givenCurrentNumber() throws UnsupportedEncodingException {
        currentNum = Integer.valueOf(given()
          .when()
          .get("/adder/current")
          .mvcResult()
          .getResponse()
          .getContentAsString());
    }

    @Step("adding {0}")
    public void whenAddNumber(int num) {
        mockMvcResponse = given()
          .queryParam("num", num)
          .when()
          .post("/adder");
        currentNum += num;
    }

    @Step("got the sum")
    public void thenSummedUp() {
        mockMvcResponse
          .then()
          .statusCode(200)
          .body(equalTo(currentNum + ""));
    }
}


5 Serenity、JBehave、そしてSpring

SerenityのSpring統合サポートは

JBehave

とシームレスに連携します。テストシナリオをJBehaveのストーリーとして書いてみましょう。

Scenario: A user can submit a number to adder and get the sum
Given a number
When I submit another number 5 to adder
Then I get a sum of the numbers

ロジックを

@ Service

に実装し、APIを介してアクションを公開することができます。

@RequestMapping(value = "/adder", produces = MediaType.APPLICATION__JSON__UTF8__VALUE)
@RestController
public class AdderController {

    private AdderService adderService;

    public AdderController(AdderService adderService) {
        this.adderService = adderService;
    }

    @GetMapping("/current")
    public int currentNum() {
        return adderService.currentBase();
    }

    @PostMapping
    public int add(@RequestParam int num) {
        return adderService.add(num);
    }
}

次のように

RestAssuredMockMvc

を使用してSerenity-JBehaveテストをビルドできます。

@ContextConfiguration(classes = {
  AdderController.class, AdderService.class })
public class AdderIntegrationTest extends SerenityStory {

    @Autowired private AdderService adderService;

    @BeforeStory
    public void init() {
        RestAssuredMockMvc.standaloneSetup(new AdderController(adderService));
    }
}

public class AdderStory {

    @Steps AdderRestSteps restSteps;

    @Given("a number")
    public void givenANumber() throws Exception{
        restSteps.givenCurrentNumber();
    }

    @When("I submit another number $num to adder")
    public void whenISubmitToAdderWithNumber(int num){
        restSteps.whenAddNumber(num);
    }

    @Then("I get a sum of the numbers")
    public void thenIGetTheSum(){
        restSteps.thenSummedUp();
    }
}


SerenityStory



@ ContextConfiguration

でマークすることしかできません。そうすると、Springインジェクションは自動的に有効になります。これは

@ Steps



@ ContextConfiguration

とまったく同じように機能します。


6. 概要

この記事では、Serenity BDDとSpringの統合方法について説明しました。統合は完全には完璧ではありませんが、確実にそこに到達しています。

いつものように、完全な実装はhttps://github.com/eugenp/tutorials/tree/master/libraries[GitHubプロジェクト]で見つけることができます。