1. 引言
之前我们介绍过 Serenity BDD 框架。本文将深入探讨如何将 Serenity BDD 与 Spring 框架集成,实现更强大的测试能力。
2. Maven 依赖
要在 Spring 项目中启用 Serenity,需要在 pom.xml
中添加以下依赖:
<dependency>
<groupId>net.serenity-bdd</groupId>
<artifactId>serenity-core</artifactId>
<version>1.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.serenity-bdd</groupId>
<artifactId>serenity-spring</artifactId>
<version>1.9.0</version>
<scope>test</scope>
</dependency>
还需要配置 serenity-maven-plugin
,这对生成 Serenity 测试报告至关重要:
<plugin>
<groupId>net.serenity-bdd.maven.plugins</groupId>
<artifactId>serenity-maven-plugin</artifactId>
<version>4.0.18</version>
<executions>
<execution>
<id>serenity-reports</id>
<phase>post-integration-test</phase>
<goals>
<goal>aggregate</goal>
</goals>
</execution>
</executions>
</plugin>
3. Spring 集成
Spring 集成测试通常使用 @RunWith(SpringJUnit4ClassRunner.class)
,但 Serenity 测试必须由 SerenityRunner
执行。为解决冲突,可使用以下规则:
3.1. SpringIntegrationMethodRule
SpringIntegrationMethodRule
是一个 MethodRule
,在 @Before
之后、@BeforeClass
之前构建 Spring 上下文。
假设有需要注入的属性:
<util:properties id="props">
<prop key="adder">4</prop>
</util:properties>
在测试中添加规则启用注入:
@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();
}
}
⚠️ 踩坑提示:当测试方法污染上下文时,可添加 @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();
}
}
问题根源:Serenity 的 Spring 集成在处理方法级 @DirtiesContext
时,只会重建当前测试实例的上下文,不会重建 @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); } }
在
@Before
中初始化状态:@Before public void init() { adderServiceSteps.givenBaseAndAdder(randomInt(), randomInt()); }
3.2. SpringIntegrationClassRule
支持类级别注解(如 @DirtiesContext(classMode = AFTER_CLASS)
):
@RunWith(SerenityRunner.class)
@ContextConfiguration(classes = AdderService.class)
public static abstract class Base {
@Steps AdderServiceSteps adderServiceSteps;
@ClassRule public static SpringIntegrationClassRule springIntegrationClassRule = new SpringIntegrationClassRule();
// 测试方法...
}
@DirtiesContext(classMode = AFTER_CLASS)
public static class DirtiesContextIntegrationTest extends Base {
// 测试实现...
}
✅ 优势:类级别的 @DirtiesContext
会重建所有隐式注入的依赖。
3.3. 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 组件时,可直接使用 RestAssuredMockMvc
替代 serenity-spring
集成。
4.1. Maven 依赖
添加 spring-mock-mvc
依赖:
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>spring-mock-mvc</artifactId>
<version>5.3.0</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
进行测试:
@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("获取当前数字")
public void givenCurrentNumber() throws UnsupportedEncodingException {
currentNum = Integer.valueOf(given()
.when()
.get("/adder/current")
.mvcResult()
.getResponse()
.getContentAsString());
}
@Step("添加数字 {0}")
public void whenAddNumber(int num) {
mockMvcResponse = given()
.queryParam("num", num)
.when()
.post("/adder");
currentNum += num;
}
@Step("验证求和结果")
public void thenSummedUp() {
mockMvcResponse
.then()
.statusCode(200)
.body(equalTo(currentNum + ""));
}
}
5. Serenity、JBehave 与 Spring 的结合
Serenity 的 Spring 集成与 JBehave 无缝协作。以下为 JBehave 故事示例:
Scenario: 用户可提交数字到加法器并获取结果
Given 一个数字
When 我提交另一个数字 5 到加法器
Then 我得到数字之和
实现逻辑:
@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);
}
}
测试实现:
@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("一个数字")
public void givenANumber() throws Exception{
restSteps.givenCurrentNumber();
}
@When("我提交另一个数字 $num 到加法器")
public void whenISubmitToAdderWithNumber(int num){
restSteps.whenAddNumber(num);
}
@Then("我得到数字之和")
public void thenIGetTheSum(){
restSteps.thenSummedUp();
}
}
✅ 关键点:在 SerenityStory
上添加 @ContextConfiguration
即可自动启用 Spring 注入,与 @Steps
上的注解效果相同。
6. 总结
本文详细介绍了 Serenity BDD 与 Spring 框架的集成方案,涵盖:
- 基础依赖配置
- 三种 Spring 集成规则(方法级/类级/便捷运行器)
- SpringMVC 测试的替代方案
- 与 JBehave 的结合使用
虽然集成方案仍有改进空间,但已能满足大多数测试场景。完整实现代码可在 GitHub 项目 中获取。