1. 概述
在本教程中,我们将学习如何在 JUnit4 中使用 Parameterized 测试运行器来参数化一个 Spring 集成测试。
2. SpringJUnit4ClassRunner
SpringJUnit4ClassRunner
是 JUnit4 中 ClassRunner
的一个实现,它将 Spring 的 TestContextManager
嵌入到 JUnit 测试中。
TestContextManager
是 Spring 测试框架的入口点,负责管理 Spring 应用上下文(ApplicationContext)以及依赖注入。因此,SpringJUnit4ClassRunner
让我们可以方便地为 Spring 组件(如 Controller、Repository)编写集成测试。
例如,我们可以为一个 RestController
编写如下集成测试:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = WebConfig.class)
public class RoleControllerIntegrationTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
private static final String CONTENT_TYPE = "application/text;charset=ISO-8859-1";
@Before
public void setup() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
@Test
public void givenEmployeeNameJohnWhenInvokeRoleThenReturnAdmin() throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders
.get("/role/John"))
.andDo(print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().contentType(CONTENT_TYPE))
.andExpect(MockMvcResultMatchers.content().string("ADMIN"));
}
}
如上所示,这个测试会模拟访问 /role/John
接口,并验证返回是否为 "ADMIN"。
如果我们想测试其他用户名/角色组合,比如 /role/Doe
返回 "EMPLOYEE",就需要再写一个新测试:
@Test
public void givenEmployeeNameDoeWhenInvokeRoleThenReturnEmployee() throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders
.get("/role/Doe"))
.andDo(print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().contentType(CONTENT_TYPE))
.andExpect(MockMvcResultMatchers.content().string("EMPLOYEE"));
}
这样写的话,当测试组合较多时,代码会显得冗长,重复度高。为了解决这个问题,我们来看看如何使用 JUnit 的 Parameterized 测试运行器。
3. 使用 Parameterized
3.1. 定义参数
JUnit 提供了 Parameterized 测试运行器,允许我们为一个测试方法提供多组输入数据,从而避免重复代码。
示例如下:
@RunWith(Parameterized.class)
@WebAppConfiguration
@ContextConfiguration(classes = WebConfig.class)
public class RoleControllerParameterizedIntegrationTest {
@Parameter(value = 0)
public String name;
@Parameter(value = 1)
public String role;
@Parameters
public static Collection<Object[]> data() {
Collection<Object[]> params = new ArrayList<>();
params.add(new Object[]{"John", "ADMIN"});
params.add(new Object[]{"Doe", "EMPLOYEE"});
return params;
}
// ...
}
上面的代码中,我们通过 @Parameters
注解定义了两组测试参数,每组参数分别对应 name
和 role
两个字段。
⚠️ 但这里有个问题:JUnit 不允许一个测试类使用多个运行器。也就是说,我们不能同时使用 @RunWith(Parameterized.class)
和 SpringJUnit4ClassRunner
。这就需要我们手动集成 Spring 的测试支持。
3.2. 手动初始化 TestContextManager
Spring 提供了 TestContextManager
类来管理测试上下文。我们可以在测试类中手动初始化它:
@RunWith(Parameterized.class)
@WebAppConfiguration
@ContextConfiguration(classes = WebConfig.class)
public class RoleControllerParameterizedIntegrationTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
private TestContextManager testContextManager;
@Before
public void setup() throws Exception {
this.testContextManager = new TestContextManager(getClass());
this.testContextManager.prepareTestInstance(this);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
@Test
public void givenEmployeeNameWhenInvokeRoleThenReturnRole() throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders
.get("/role/" + name))
.andDo(print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().contentType(CONTENT_TYPE))
.andExpect(MockMvcResultMatchers.content().string(role));
}
}
✅ 该方法可行,但 不推荐。因为手动管理 TestContextManager
容易出错,也不够优雅。
3.3. 使用 SpringClassRule 与 SpringMethodRule
Spring 推荐使用 SpringClassRule
和 SpringMethodRule
来替代手动初始化。它们是 JUnit 的 TestRule
实现,可以替代传统的 @Before
, @After
等注解进行测试上下文管理。
SpringClassRule
: 提供类级别的上下文初始化SpringMethodRule
: 提供方法级别的上下文初始化
示例代码如下:
@RunWith(Parameterized.class)
@WebAppConfiguration
@ContextConfiguration(classes = WebConfig.class)
public class RoleControllerParameterizedClassRuleIntegrationTest {
@ClassRule
public static final SpringClassRule scr = new SpringClassRule();
@Rule
public final SpringMethodRule smr = new SpringMethodRule();
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
@Test
public void givenEmployeeNameWhenInvokeRoleThenReturnRole() throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders
.get("/role/" + name))
.andDo(print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().contentType(CONTENT_TYPE))
.andExpect(MockMvcResultMatchers.content().string(role));
}
}
✅ 这种方式更推荐使用,因为 Spring 官方明确建议使用 SpringClassRule
+ SpringMethodRule
来替代手动初始化。
4. 总结
本文介绍了两种在 JUnit4 中使用 Parameterized 测试运行器进行 Spring 集成测试的方法:
- ✅ 手动初始化
TestContextManager
(不推荐) - ✅ 使用
SpringClassRule
+SpringMethodRule
(推荐)
虽然本文只讨论了 Parameterized 运行器,但这些方法也适用于其他 JUnit 测试运行器。使用参数化测试可以大幅减少重复测试代码,提高测试覆盖率和可维护性。
所有示例代码已上传至 GitHub:点击访问