SpringとJBehaveを備えたSerenityBDD

1.はじめに

以前、SerenityBDDフレームワークを導入しました。

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

2.Mavenの依存関係

SpringプロジェクトでSerenityを有効にするには、pom.xmlにserenity-coreserenity-springを追加する必要があります。

 net.serenity-bdd serenity-core 1.4.0 test   net.serenity-bdd serenity-spring 1.4.0 test 

また、serenity-maven-pluginを構成する必要があります。これは、Serenityテストレポートを生成するために重要です。

 net.serenity-bdd.maven.plugins serenity-maven-plugin 1.4.0   serenity-reports post-integration-test  aggregate    

3.春の統合

Spring統合テストは@ RunWithSpringJUnit4ClassRunnerにする必要があります。セレニティテストがで実行する必要があるとして、しかし、我々は、セレニティと直接テストランナーを使用することはできませんSerenityRunner

Serenityを使用したテストでは、SpringIntegrationMethodRuleSpringIntegrationClassRuleを使用してインジェクションを有効にできます。

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

3.1。SpringIntegrationMethodRule

SpringIntegrationMethodRuleであるMethodRule試験方法に適用されます。Springコンテキストは、@ Beforeの前と@BeforeClassの後に構築されます。

Beanに注入するプロパティがあるとします。

 4 

次に、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テストのメソッドレベルのアノテーションもサポートしています。一部のテストメソッドがテストコンテキストを汚す場合は、@ 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の明示的な依存関係としてサービスを作成できます。

@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統合

In cases when we only need to test SpringMVC components with Serenity, we can simply make use of RestAssuredMockMvc in rest-assured instead of the serenity-spring integration.

4.1. Maven Dependency

We need to add the rest-assured spring-mock-mvc dependency to the pom.xml:

 io.rest-assured spring-mock-mvc 3.0.3 test 

4.2. RestAssuredMockMvc in Action

Let's now test the following controller:

@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; } }

We can take advantage of the MVC-mocking utilities of RestAssuredMockMvc like this:

@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(); } }

Then the rest part is no different from how we use 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, and Spring

Serenity's Spring integration support works seamlessly with JBehave. Let's write our test scenario as a JBehave story:

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.まとめ

この記事では、SerenityBDDをSpringと統合する方法について説明しました。統合は完全ではありませんが、確実に実現しています。

いつものように、完全な実装はGitHubプロジェクトにあります。