Java >> Java tutorial >  >> Tag >> Spring

Serenity BDD med Spring og JBehave

1. Introduktion

Tidligere har vi introduceret Serenity BDD-rammen.

I denne artikel vil vi introducere, hvordan du integrerer Serenity BDD med Spring.

2. Maven Dependency

For at aktivere Serenity i vores forårsprojekt skal vi tilføje serenity-core og serenity-spring til 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>

Vi skal også konfigurere serenity-maven-plugin , hvilket er vigtigt for at generere Serenity-testrapporter:

<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 Integration

Spring integrationstest skal @RunWith SpringJUnit4ClassRunner . Men vi kan ikke bruge testløberen direkte med Serenity, da Serenity-test skal køres af SerenityRunner .

Til test med Serenity kan vi bruge SpringIntegrationMethodRule og SpringIntegrationClassRule for at aktivere injektion.

Vi baserer vores test på et simpelt scenarie:givet et tal, når du tilføjer et andet tal, returnerer det derefter summen.

3.1. SpringIntegrationMethodRule

SpringIntegrationMethodRule er en MetodeRule anvendt på testmetoderne. Forårskonteksten vil blive bygget før @Before og efter @BeforeClass .

Antag, at vi har en egenskab at injicere i vores bønner:

<util:properties id="props">
 <prop key="adder">4</prop>
</util:properties>

Lad os nu tilføje SpringIntegrationMethodRule for at aktivere værdiinjektionen i vores test:

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

Det understøtter også annoteringer på metodeniveau af forårstest . Hvis en testmetode snavser testkonteksten, kan vi markere @DirtiesContext på den:

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

}

I eksemplet ovenfor, når vi kalder adderServiceSteps.whenAccumulate() , grundnummerfeltet for @Service indsprøjtet i adderServiceSteps vil blive ændret:

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

}

Konkret tildeler vi summen til basisnummeret:

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

I den første test _0_givenNumber_whenAddAndAccumulate_thenSummedUp , ændres basisnummeret, hvilket gør konteksten beskidt. Når vi forsøger at tilføje et andet tal, får vi ikke en forventet sum.

Bemærk, at selvom vi markerede den første test med @DirtiesContext , den anden test er stadig påvirket:efter tilføjelse er summen stadig forkert. Hvorfor?

Nu, mens du behandler metodeniveau @DirtiesContext , Serenitys Spring-integration genopbygger kun testkonteksten for den aktuelle testinstans. Den underliggende afhængighedskontekst i @Steps vil ikke blive genopbygget.

For at omgå dette problem kan vi injicere @Service i vores nuværende testinstans, og gør tjenesten som en eksplicit afhængighed af @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;
 }

 // ...
}

Eller vi kan sætte initialiseringstrinnet i @Before afsnit for at undgå beskidt kontekst. Men denne form for løsning er muligvis ikke tilgængelig i nogle komplekse situationer.

@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

For at aktivere annoteringer på klasseniveau bør vi bruge SpringIntegrationClassRule . Lad os sige, at vi har følgende testklasser; hver beskidte konteksten:

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

I dette eksempel vil alle implicitte injektioner blive genopbygget til klasseniveau @DirtiesContext .

3.3. SpringIntegrationSerenityRunner

Der er en praktisk klasse SpringIntegrationSerenityRunner der automatisk tilføjer begge integrationsregler ovenfor. Vi kan køre test ovenfor med denne runner for at undgå at specificere metoden eller klassetestreglerne i vores test:

@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 Integration

I tilfælde, hvor vi kun skal teste SpringMVC-komponenter med Serenity, kan vi blot gøre brug af RestAssuredMockMvc i rolig i stedet for serenity-spring integration.

4.1. Maven Dependency

Vi er nødt til at tilføje den rolige spring-mock-mvc-afhængighed til 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 i aktion

Lad os nu teste følgende 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;
 }
}

Vi kan drage fordel af MVC-hånende hjælpeprogrammer i RestAssuredMockMvc sådan her:

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

Så er resten ikke anderledes end, hvordan vi bruger 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 og Spring

Serenitys Spring-integrationssupport fungerer problemfrit med JBehave. Lad os skrive vores testscenarie som en JBehave-historie:

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

Vi kan implementere logikken i en @Service og eksponer handlingerne via API'er:

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

Nu kan vi bygge Serenity-JBehave-testen ved hjælp af RestAssuredMockMvc som følger:

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

Vi kan kun markere SerenityStory med @ContextConfiguration , så aktiveres fjederindsprøjtning automatisk. Dette fungerer ganske det samme som @ContextConfiguration@Steps .

6. Resumé

I denne artikel dækkede vi, hvordan man integrerer Serenity BDD med Spring. Integrationen er ikke helt perfekt, men den er helt sikkert på vej.

Som altid kan den fulde implementering findes på GitHub-projektet.


Java tag