Рубрики
Без рубрики

Безмятежность БДД с весной и поведением

Быстрый и практический пример безмятежности с Spring и JBehave.

Автор оригинала: baeldung.

1. введение

Ранее мы представили фреймворк Serenity BDD .

В этой статье мы расскажем, как интегрировать Serenity BDD с Spring.

2. Зависимость Maven

Чтобы включить безмятежность в наш весенний проект, нам нужно добавить безмятежность-мужество и безмятежность-весну в pom.xml :


    net.serenity-bdd
    serenity-core
    1.4.0
    test


    net.serenity-bdd
    serenity-spring
    1.4.0
    test

Нам также необходимо настроить serenity-maven-плагин , который важен для создания отчетов о тестировании Serenity:


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

3. Интеграция Пружин

Тест интеграции Spring должен @RunWith |/SpringJUnit4ClassRunner . Но мы не можем использовать тестовый бегун непосредственно с Serenity, так как тесты Serenity должны выполняться SerenityRunner .

Для тестов с Serenity мы можем использовать Правило метода интеграции Spring и Правило класса интеграции Spring для включения инъекции.

Мы будем основывать наш тест на простом сценарии: заданное число при добавлении другого числа возвращает сумму.

3.1. Правило метода весеннего интегрирования

Правило метода интегрирования пружины является правилом метода , применяемым к методам тестирования. Контекст Spring будет построен до @Before и после @BeforeClass .

Предположим, у нас есть свойство, которое нужно ввести в наши бобы:


    4

Теперь давайте добавим Правило метода интеграции Spring , чтобы включить инъекцию значений в нашем тесте:

@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 . Если какой-либо метод теста загрязняет контекст теста, мы можем пометить на нем @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.когда Accumulate() , поле базового номера @Service , введенное в adderServiceSteps , будет изменено:

@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_given Number_ Когда вы Добавляете И Накапливаете , а затем Суммируете , базовый номер изменяется, что делает контекст грязным. Когда мы попытаемся добавить еще одно число, мы не получим ожидаемой суммы.

Обратите внимание , что даже если мы отметили первый тест с помощью @DirtiesContext , второй тест все равно будет затронут: после добавления сумма все равно будет неправильной. Почему?

Теперь, при обработке уровня метода @DirtiesContext , весенняя интеграция Serenity только перестраивает контекст теста для текущего экземпляра теста. Базовый контекст зависимостей в @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;
    }

    // ...
}

Или мы можем поместить шаг инициализации условия в раздел @Перед , чтобы избежать грязного контекста. Но такое решение может быть недоступно в некоторых сложных ситуациях.

@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. Правило класса Весенней интеграции

Чтобы включить аннотации уровня класса, мы должны использовать Правило класса интеграции Spring . Допустим, у нас есть следующие тестовые классы; каждый из них загрязняет контекст:

@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. Весенняя интеграция SerenityRunner

Существует удобный класс Spring Integration SerenityRunner , который автоматически добавляет оба правила интеграции выше. Мы можем запускать тесты выше с помощью этого бегуна, чтобы избежать указания правил тестирования метода или класса в нашем тесте:

@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. Интеграция Spring MVC

В тех случаях, когда нам нужно только протестировать компоненты Spring MVC с помощью Serenity, мы можем просто использовать RestAssuredMockMvc в rest-assured вместо интеграции serenity-spring .

4.1. Зависимость Maven

Нам нужно добавить зависимость rest-assured spring-mock-mvc в pom.xml :


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

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

Мы можем воспользоваться преимуществами утилит MVC-mocking из RestAssuredMockMvc , как это:

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

Тогда остальная часть ничем не отличается от того, как мы используем будьте уверены :

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. Безмятежность, JBehave и весна

Поддержка весенней интеграции Serenity легко работает с 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);
    }
}

Теперь мы можем построить тест Serenity-JBehave с помощью RestAssuredMockMvc следующим образом:

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

Мы можем только пометить Serenity Story с помощью @ContextConfiguration , тогда впрыск пружины включается автоматически. Это работает точно так же, как @ContextConfiguration на @Steps .

6. Резюме

В этой статье мы рассказали о том, как интегрировать Serenity BDD с Spring. Интеграция не совсем идеальна, но она определенно приближается к этому.

Как всегда, полную реализацию можно найти на проекте GitHub .