1. 概述
在本教程中,我們將學習如何使用 JUnit4 和 參數化測試運行器 對 Spring 集成測試進行參數化,該測試在 JUnit4 中實現。
2. SpringJUnit4ClassRunner
SpringJUnit4ClassRunner 是 JUnit4 的 ClassRunner 的一個實現,它將 Spring 的 TestContextManager 嵌入到 JUnit 測試中。
TestContextManager 是 Spring 的 TestContext 框架的入口點,因此它管理了 JUnit 測試類中對 Spring ApplicationContext 的訪問以及依賴注入。
因此,SpringJUnit4ClassRunner 允許開發者為 Spring 組件(如控制器和存儲庫)實現集成測試。
例如,我們可以為我們的 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"));
}
}如測試結果所示,我們的 Controller 接受用户名作為路徑參數,並返回相應的用户角色。
現在,為了使用不同的用户名/角色組合測試此 REST 服務,我們需要實現一個新的測試:
@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 測試。
3. 使用參數化
3.1. 定義參數
參數化 是一個自定義 JUnit 測試運行器,允許我們編寫單個測試用例並將其針對多個輸入參數運行。
@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;
}
//...
}如上所示,我們使用了<em>@Parameters</em>註解來準備注入到 JUnit 測試中的輸入參數。我們還為<em>@Parameter</em>字段的<em>name</em>和<em>role</em>提供了映射。
但是現在,我們面臨另一個需要解決的問題——<strong>JUnit 不允許在一個 JUnit 測試類中包含多個運行者</strong>。這意味着<strong>我們無法利用<em>SpringJUnit4ClassRunner</em>來嵌入<em>TestContextManager</em>到我們的測試類中。我們需要找到另一種嵌入<em>TestContextManager</em>的方法。
幸運的是,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();
}
//...
}特別地,在這個示例中,我們使用了參數化運行器,而不是SpringJUnit4ClassRunner。接下來,我們在setup()方法中初始化了TestContextManager。
現在,我們可以實現我們的參數化 JUnit 測試:
@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));
}JUnit 將會執行此測試用例兩次 — 針對我們使用 @Parameters 註解定義的每個輸入集。
3.3. <em >SpringClassRule</em > 和 <em >SpringMethodRule</em >
通常,不建議手動初始化 <em >TestContextManager</em >。相反,Spring 建議使用 <em >SpringClassRule</em > 和 <em >SpringMethodRule</em >。
<em >SpringClassRule</em > 實現了 JUnit 的 <em >TestRule</em >——一種替代方案來編寫測試用例。<em >TestRule</em > 可用於替換以前使用@Before、<em >@BeforeClass</em >、@After 和 `@AfterClass 方法進行的設置和清理操作。
<em >SpringClassRule</em > 將 <em >TestContextManager</em > 的類級別功能嵌入到 JUnit 測試類中。它初始化 <em >TestContextManager</em > 並調用 Spring <em >TestContext</em > 的設置和清理。因此,它提供依賴注入和訪問ApplicationContext 的功能。
除了 <em >SpringClassRule</em > 之外,我們還需要使用 <em >SpringMethodRule</em >,它為 `TestContextManager 提供實例級別和方法級別的功能。
<em >SpringMethodRule</em > 負責測試方法的準備。它還檢查標記為跳過的測試用例,並阻止它們運行。
@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();
@Before
public void setup() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
//...
}4. 結論
在本文中,我們探討了使用 Parameterized 測試運行器代替 SpringJUnit4ClassRunner 的兩種方式來實現 Spring 集成測試的方法。我們看到了如何手動初始化 TestContextManager,並提供了一個使用 SpringClassRule 與 SpringMethodRule 的示例,該方法是 Spring 推薦的方法。
儘管我們僅在本文中討論了 Parameterized 運行器,但 實際上,我們可以使用這些方法與任何 JUnit 運行器一起編寫 Spring 集成測試。