SpringとJBehaveでのSerenity BDD
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プロジェクト]で見つけることができます。