Serenity BDD met Spring en JBehave

1. Inleiding

Eerder hebben we het Serenity BDD-framework geïntroduceerd.

In dit artikel laten we zien hoe u Serenity BDD integreert met Spring.

2. Maven Afhankelijkheid

Om Serenity in ons voorjaarsproject mogelijk te maken, moeten we toevoegen sereniteit-kern en sereniteit-lente naar de pom.xml:

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

We moeten ook het serenity-maven-plugin, wat belangrijk is voor het genereren van Serenity-testrapporten:

 net.serenity-bdd.maven.plugins serenity-maven-plugin 1.4.0 serenity-reports post-integratietest geaggregeerd 

3. Lente-integratie

Voorjaarsintegratietest moet @Rennen metSpringJUnit4ClassRunner. Maar we kunnen de testrunner niet rechtstreeks met Serenity gebruiken, omdat Serenity-tests moeten worden uitgevoerd SerenityRunner.

Voor tests met Serenity kunnen we gebruiken SpringIntegrationMethodRule en SpringIntegrationClassRule om injectie mogelijk te maken.

We baseren onze test op een eenvoudig scenario: een getal gegeven, bij het toevoegen van een ander getal, retourneert de som.

3.1. SpringIntegrationMethodRule

SpringIntegrationMethodRule is een MethodRule toegepast op de testmethoden. De Spring-context zal eerder worden gebouwd @Voordat en daarna @Voor klas.

Stel dat we een eigenschap hebben om in onze bonen te injecteren:

 4 

Laten we nu toevoegen SpringIntegrationMethodRule om de waarde-injectie in onze test mogelijk te maken:

@RunWith (SerenityRunner.class) @ContextConfiguration (locations = "classpath: adder-beans.xml") openbare klasse AdderMethodRuleIntegrationTest {@Rule openbare SpringIntegrationMethodRule springMethodIntegration = nieuwe SpringIntegrationMethodRule (); @Steps privé AdderSteps adderSteps; @Value ("# {props ['adder']}") private int adder; @Test openbare ongeldig gegeven number_whenAdd_thenSummedUp () {adderSteps.givenNumber (); adderSteps.whenAdd (adder); adderSteps.thenSummedUp (); }}

Het ondersteunt ook annotaties op het niveau van lente test. Als een testmethode de testcontext vervuilt, kunnen we markeren @DirtiesContext ben ermee bezig:

@RunWith (SerenityRunner.class) @FixMethodOrder (MethodSorters.NAME_ASCENDING) @ContextConfiguration (klassen = AdderService.class) openbare klasse AdderMethodDirtiesContextIntegrationTest {@Steps privé AdderServiceSteps adderServiceSteps; @Rule openbaar SpringIntegrationMethodRule springIntegration = nieuwe SpringIntegrationMethodRule (); @DirtiesContext @Test openbare leegte _0_givenNumber_whenAddAndAccumulate_thenSummedUp () {adderServiceSteps.givenBaseAndAdder (randomInt (), randomInt ()); adderServiceSteps.whenAccumulate (); adderServiceSteps.summedUp (); adderServiceSteps.whenAdd (); adderServiceSteps.sumWrong (); } @Test openbare ongeldige _1_givenNumber_whenAdd_thenSumWrong () {adderServiceSteps.whenAdd (); adderServiceSteps.sumWrong (); }}

In het bovenstaande voorbeeld, wanneer we aanroepen adderServiceSteps.whenAccumulate (), het basisnummerveld van het @Onderhoud geïnjecteerd adderServiceSteps zal veranderd worden:

@ContextConfiguration (klassen = AdderService.class) openbare klasse AdderServiceSteps {@Autowired privé AdderService adderService; private int gegevenNummer; privé int basis; privé int bedrag; openbare ongeldige gegevenBaseAndAdder (int base, int adder) {this.base = base; adderService.baseNum (basis); this.givenNumber = adder; } public void whenAdd () {sum = adderService.add (gegevenNummer); } public void summedUp () {assertEquals (base + givenNumber, som); } public void sumWrong () {assertNotEquals (base + givenNumber, som); } public void whenAccumulate () {sum = adderService.accumulate (givenNumber); }}

Concreet wijzen we de som toe aan het basisgetal:

@Service openbare klasse AdderService {privé 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; }}

In de eerste test _0_givenNumber_whenAddAndAccumulate_thenSummedUp, wordt het basisnummer gewijzigd, waardoor de context vuil wordt. Als we proberen een ander nummer toe te voegen, krijgen we geen verwacht bedrag.

Merk op dat zelfs als we de eerste test hebben gemarkeerd met @DirtiesContext, de tweede test wordt nog steeds beïnvloed: na het toevoegen is de som nog steeds verkeerd. Waarom?

Nu, terwijl de verwerkingsmethode niveau @DirtiesContext, Herstelt de Spring-integratie van Serenity alleen de testcontext voor de huidige testinstantie. De onderliggende afhankelijkheidscontext in @Stappen zal niet worden herbouwd.

Om dit probleem te omzeilen, kunnen we de @Onderhoud in onze huidige testinstantie, en maak service als een expliciete afhankelijkheid van @Stappen:

@RunWith (SerenityRunner.class) @FixMethodOrder (MethodSorters.NAME_ASCENDING) @ContextConfiguration (klassen = AdderService.class) openbare klasse AdderMethodDirtiesContextDependencyWorkaroundIntegrationTest {private AdderCepsonstructor adderStependency; @Autowired private AdderService adderService; @Before public void init () {adderSteps = nieuwe AdderConstructorDependencySteps (adderService); } // ...}
openbare klasse AdderConstructorDependencySteps {privé AdderService adderService; openbare AdderConstructorDependencySteps (AdderService adderService) {this.adderService = adderService; } // ...}

Of we kunnen de initialisatiestap van de conditie in het @Voordat sectie om vuile context te vermijden. Maar dit soort oplossing is mogelijk niet beschikbaar in sommige complexe situaties.

@RunWith (SerenityRunner.class) @FixMethodOrder (MethodSorters.NAME_ASCENDING) @ContextConfiguration (classes = AdderService.class) openbare klasse AdderMethodDirtiesContextInitWorkaroundIntegrationTest {@Steps private AdderServiceSteps adderServiceSteps adderServiceSteps; @Before public void init () {adderServiceSteps.givenBaseAndAdder (randomInt (), randomInt ()); } // ...}

3.2. SpringIntegrationClassRule

Om annotaties op klasniveau mogelijk te maken, moeten we SpringIntegrationClassRule. Stel dat we de volgende testklassen hebben; elk vervuilt de context:

@RunWith (SerenityRunner.class) @ContextConfiguration (klassen = AdderService.class) openbare statische abstracte klasse Base {@Steps AdderServiceSteps adderServiceSteps; @ClassRule openbare statische SpringIntegrationClassRule springIntegrationClassRule = nieuwe 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) openbare statische klasse DirtiesContextIntegrationTest breidt Base {@Test openbare leegte gegevenNumber_whenAdd_thenSumWrong () {super.whenAdd_thenSummedUp () uit; adderServiceSteps.givenBaseAndAdder (randomInt (), randomInt ()); super.whenAccumulate_thenSummedUp (); super.whenAdd_thenSumWrong (); }}
@DirtiesContext (classMode = AFTER_CLASS) openbare statische klasse AnotherDirtiesContextIntegrationTest breidt Base uit {@Test openbare leegte gegevenNumber_whenAdd_thenSumWrong () {super.whenAdd_thenSummedUp (); adderServiceSteps.givenBaseAndAdder (randomInt (), randomInt ()); super.whenAccumulate_thenSummedUp (); super.whenAdd_thenSumWrong (); }}

In dit voorbeeld worden alle impliciete injecties opnieuw opgebouwd voor klasseniveau @DirtiesContext.

3.3. SpringIntegrationSerenityRunner

Er is een handige les SpringIntegrationSerenityRunner die automatisch beide bovenstaande integratieregels toevoegt. We kunnen bovenstaande tests uitvoeren met deze hardloper om te voorkomen dat we de testregels voor methoden of klassen in onze test specificeren:

@RunWith (SpringIntegrationSerenityRunner.class) @ContextConfiguration (locations = "classpath: adder-beans.xml") openbare klasse AdderSpringSerenityRunnerIntegrationTest {@Steps privé AdderSteps adderSteps; @Value ("# {props ['adder']}") private int adder; @Test openbare ongeldig gegeven number_whenAdd_thenSummedUp () {adderSteps.givenNumber (); adderSteps.whenAdd (adder); adderSteps.thenSummedUp (); }}

4. SpringMVC-integratie

In gevallen waarin we alleen SpringMVC-componenten met Serenity hoeven te testen, kunnen we er gewoon gebruik van maken RestAssuredMockMvc in gerust in plaats van de sereniteit-lente integratie.

4.1. Afhankelijkheid van Maven

We moeten de geruststellende spring-mock-mvc-afhankelijkheid toevoegen aan het pom.xml:

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

4.2. RestAssuredMockMvc in actie

Laten we nu de volgende controller testen:

@RequestMapping (value = "/ adder", produceert = MediaType.APPLICATION_JSON_UTF8_VALUE) @RestController openbare klasse PlainAdderController {privé finale int currentNumber = RandomUtils.nextInt (); @GetMapping ("/ current") public int currentNum () {return currentNumber; } @PostMapping public int add (@RequestParam int num) {return currentNumber + num; }}

We kunnen profiteren van de MVC-mocking-hulpprogramma's van RestAssuredMockMvc soortgelijk:

@RunWith (SerenityRunner.class) openbare klasse AdderMockMvcIntegrationTest {@Before openbare void init () {RestAssuredMockMvc.standaloneSetup (nieuwe PlainAdderController ()); } @Steps AdderRestSteps stappen; @Test openbare leegte gegevenNumber_whenAdd_thenSummedUp () gooit Uitzondering {steps.givenCurrentNumber (); steps.whenAddNumber (randomInt ()); stappen.thenSummedUp (); }}

Dan verschilt het restgedeelte niet van hoe we het gebruiken wees gerustgesteld:

openbare klasse AdderRestSteps {privé MockMvcResponse mockMvcResponse; privé int currentNum; @Step ("get the current number") public void givenCurrentNumber () gooit UnsupportedEncodingException {currentNum = Integer.valueOf (gegeven () .when () .get ("/ adder / current") .mvcResult () .getResponse (). getContentAsString ()); } @Step ("add {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 en Spring

De Spring-integratieondersteuning van Serenity werkt naadloos samen met JBehave. Laten we ons testscenario schrijven als een JBehave-verhaal:

Scenario: een gebruiker kan een getal indienen bij de opteller en de som krijgen. Gegeven een getal Wanneer ik een ander getal 5 aan de opteller geef, krijg ik een som van de getallen

We kunnen de logica implementeren in een @Onderhoud en stel de acties bloot via API's:

@RequestMapping (value = "/ adder", produceert = MediaType.APPLICATION_JSON_UTF8_VALUE) @RestController openbare klasse AdderController {privé AdderService adderService; openbare 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 kunnen we de Serenity-JBehave-test bouwen met behulp van RestAssuredMockMvc als volgt:

@ContextConfiguration (klassen = {AdderController.class, AdderService.class}) openbare klasse AdderIntegrationTest breidt SerenityStory uit {@Autowired private AdderService adderService; @BeforeStory public void init () {RestAssuredMockMvc.standaloneSetup (nieuwe AdderController (adderService)); }}
openbare klasse AdderStory {@Steps AdderRestSteps restSteps; @Given ("a number") public void givenANumber () gooit uitzondering {restSteps.givenCurrentNumber (); } @When ("Ik dien een ander nummer $ num in bij adder") public void whenISubmitToAdderWithNumber (int num) {restSteps.whenAddNumber (num); } @Then ("Ik krijg een som van de getallen") public void thenIGetTheSum () {restSteps.thenSummedUp (); }}

We kunnen alleen markeren SerenityStory met @ContextConfiguration, dan wordt Spring injection automatisch ingeschakeld. Dit werkt ongeveer hetzelfde als het @ContextConfiguration Aan @Stappen.

6. Samenvatting

In dit artikel hebben we besproken hoe u Serenity BDD met Spring kunt integreren. De integratie is niet helemaal perfect, maar het komt er zeker wel.

Zoals altijd is de volledige implementatie te vinden op het GitHub-project.