Serenity BDD dengan Spring dan JBehave

1. Perkenalan

Sebelumnya, kami telah memperkenalkan framework Serenity BDD.

Di artikel ini, kami akan memperkenalkan cara mengintegrasikan Serenity BDD dengan Spring.

2. Ketergantungan Maven

Untuk mengaktifkan Serenity di proyek Spring kita, kita perlu menambahkan serenity-core dan serenity-spring ke pom.xml :

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

Kami juga perlu mengonfigurasi serenity-maven-plugin , yang penting untuk membuat laporan pengujian Serenity:

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

3. Integrasi Musim Semi

Uji integrasi pegas perlu @RunWith SpringJUnit4ClassRunner . Namun kami tidak dapat menggunakan runner pengujian secara langsung dengan Serenity, karena pengujian Serenity harus dijalankan oleh SerenityRunner .

Untuk pengujian dengan Serenity, kita dapat menggunakan SpringIntegrationMethodRule dan SpringIntegrationClassRule untuk mengaktifkan injeksi.

Kami akan mendasarkan pengujian kami pada skenario sederhana: diberi angka, saat menambahkan angka lain, lalu mengembalikan jumlahnya.

3.1. SpringIntegrationMethodRule

SpringIntegrationMethodRule adalah MethodRule yang diterapkan ke metode pengujian. Konteks Spring akan dibuat sebelum @Before dan setelah @BeforeClass .

Misalkan kita memiliki properti untuk disuntikkan ke dalam biji kita:

 4 

Sekarang mari tambahkan SpringIntegrationMethodRule untuk mengaktifkan injeksi nilai dalam pengujian kita:

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

Ini juga mendukung anotasi tingkat metode uji pegas . Jika beberapa metode pengujian mengotori konteks pengujian, kita dapat menandai @DirtiesContext di atasnya:

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

Dalam contoh di atas, saat kita memanggil adderServiceSteps.whenAccumulate () , bidang nomor dasar @Service yang dimasukkan ke adderServiceSteps akan diubah:

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

Secara khusus, kami menetapkan jumlah ke nomor dasar:

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

Pada tes pertama _0_givenNumber_whenAddAndAccumulate_thenSummedUp , nomor basis diubah, membuat konteksnya kotor. Saat kami mencoba menambahkan angka lain, kami tidak akan mendapatkan jumlah yang diharapkan.

Perhatikan bahwa meskipun kita menandai pengujian pertama dengan @DirtiesContext , pengujian kedua masih terpengaruh: setelah menambahkan, jumlahnya masih salah. Mengapa?

Sekarang, saat memproses level metode @DirtiesContext , integrasi Serenity Spring hanya membangun kembali konteks pengujian untuk instance pengujian saat ini. Konteks ketergantungan yang mendasari di @Steps tidak akan dibuat ulang.

Untuk mengatasi masalah ini, kita bisa memasukkan @Service dalam contoh pengujian kita saat ini, dan menjadikan layanan sebagai ketergantungan eksplisit @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; } // ... }

Atau kita dapat meletakkan langkah inisialisasi kondisi di bagian @Before untuk menghindari konteks yang kotor. Tetapi solusi semacam ini mungkin tidak tersedia dalam beberapa situasi yang kompleks.

@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

Untuk mengaktifkan anotasi tingkat kelas, kita harus menggunakan SpringIntegrationClassRule . Katakanlah kita memiliki kelas tes berikut; setiap mengotori konteksnya:

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

Dalam contoh ini, semua injeksi implisit akan dibuat ulang untuk @DirtiesContext level kelas .

3.3. SpringIntegrationSerenityRunner

Ada kelas SpringIntegrationSerenityRunner yang nyaman yang secara otomatis menambahkan kedua aturan integrasi di atas. Kita dapat menjalankan pengujian di atas dengan runner ini untuk menghindari penentuan metode atau aturan pengujian kelas dalam pengujian kita:

@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. Integrasi SpringMVC

Jika kita hanya perlu menguji komponen SpringMVC dengan Serenity, kita dapat menggunakan RestAssuredMockMvc dengan tenang alih-alih integrasi serenity-spring .

4.1. Ketergantungan Maven

Kita perlu menambahkan dependensi spring-mock-mvc yang terjamin ke pom.xml :

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

4.2. RestAssuredMockMvc dalam Aksi

Sekarang mari kita uji pengontrol berikut:

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

Kami dapat memanfaatkan utilitas yang mengejek MVC dari RestAssuredMockMvc seperti ini:

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

Kemudian bagian lainnya tidak berbeda dari cara kami menggunakan yakinlah :

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. Ketenangan, JBehave, dan Musim Semi

Dukungan integrasi Serenity Spring bekerja dengan lancar dengan JBehave. Mari tulis skenario pengujian kami sebagai cerita 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

Kita dapat mengimplementasikan logika di @Service dan mengekspos tindakan melalui 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); } }

Sekarang kita dapat membuat pengujian Serenity-JBehave dengan bantuan RestAssuredMockMvc sebagai berikut:

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

Kami hanya dapat menandai SerenityStory dengan @ContextConfiguration , lalu injeksi Spring diaktifkan secara otomatis. Ini bekerja cukup sama dengan @ContextConfiguration di @Steps .

6. Ringkasan

Pada artikel ini, kami membahas tentang cara mengintegrasikan Serenity BDD dengan Spring. Integrasinya tidak cukup sempurna, tetapi sudah pasti menuju ke sana.

Seperti biasa, implementasi penuh dapat ditemukan di proyek GitHub.