1. 概述
通常,我們使用 JUnit 註解,如 <em @BeforeEach, @AfterEach, @BeforeAll, @AfterAll</em>,來編排測試的生命週期,但有時這還不夠——尤其是在我們使用 Spring 框架時。
這正是 Spring 的 <em TestExecutionListener</em>> 發揮作用的地方。
在本教程中,我們將看到 <em TestExecutionListener</em>> 提供什麼,Spring 提供的默認監聽器以及如何實現自定義 <em TestExecutionListener</em>>。
2. TestExecutionListener 接口
首先,讓我們訪問 TestExecutionListener 接口:
public interface TestExecutionListener {
default void beforeTestClass(TestContext testContext) throws Exception {};
default void prepareTestInstance(TestContext testContext) throws Exception {};
default void beforeTestMethod(TestContext testContext) throws Exception {};
default void afterTestMethod(TestContext testContext) throws Exception {};
default void afterTestClass(TestContext testContext) throws Exception {};
}此接口的實現可以接收在不同測試執行階段發生的事件。因此,接口中的每個方法都會接收一個 TestContext 對象。
該 TestContext 對象包含 Spring 上下文以及目標測試類和方法的相關信息。這些信息可用於修改測試的行為或擴展其功能。
現在,讓我們快速瞭解一下這些方法:
- afterTestClass – 在測試類內執行所有測試後,對測試類進行後處理 即在所有測試執行完畢後
- afterTestExecution – 在提供的測試上下文中,在測試方法執行完畢後立即對測試進行後處理 即在測試方法執行完畢後立即
- afterTestMethod – 對測試進行後處理,在底層測試框架的 before-lifecycle 回調執行完畢後
- beforeTestClass – 在測試類內執行所有測試之前,對測試類進行預處理 即在所有測試執行之前
- beforeTestExecution – 在提供的測試上下文中,在測試方法執行之前立即對測試進行預處理 即在測試方法執行之前立即
- beforeTestMethod – 對測試進行預處理,在底層測試框架的 before-lifecycle 回調執行完畢之前
- prepareTestInstance – 準備提供的測試上下文中的測試實例
值得注意的是,此接口為所有方法提供了空默認實現。因此,具體的實現可以選擇覆蓋適合其任務的那些方法。
3. Spring 的默認 TestExecutionListener
默認情況下,Spring 提供了一些 TestExecutionListener 實現。
我們快速瞭解一下這些實現:
- ServletTestExecutionListener – 為 WebApplicationContext 配置 Servlet API 模擬對象
- DirtiesContextBeforeModesTestExecutionListener – 處理 @DirtiesContext 註解的“before” 模式
- DependencyInjectionTestExecutionListener – 為測試實例提供依賴注入
- DirtiesContextTestExecutionListener – 處理 @DirtiesContext 註解的“after” 模式
- TransactionalTestExecutionListener – 提供帶有默認回滾語義的事務測試執行
- SqlScriptsTestExecutionListener – 運行使用 @Sql 註解配置的 SQL 腳本
這些監聽器在列表中列出的順序中預先註冊。當我們創建自定義 TestExecutionListener 時,我們會更詳細地瞭解這個順序。
4. 使用自定義 TestExecutionListener
現在,讓我們定義一個自定義的 TestExecutionListener:
public class CustomTestExecutionListener implements TestExecutionListener, Ordered {
private static final Logger logger = LoggerFactory.getLogger(CustomTestExecutionListener.class);
public void beforeTestClass(TestContext testContext) throws Exception {
logger.info("beforeTestClass : {}", testContext.getTestClass());
};
public void prepareTestInstance(TestContext testContext) throws Exception {
logger.info("prepareTestInstance : {}", testContext.getTestClass());
};
public void beforeTestMethod(TestContext testContext) throws Exception {
logger.info("beforeTestMethod : {}", testContext.getTestMethod());
};
public void afterTestMethod(TestContext testContext) throws Exception {
logger.info("afterTestMethod : {}", testContext.getTestMethod());
};
public void afterTestClass(TestContext testContext) throws Exception {
logger.info("afterTestClass : {}", testContext.getTestClass());
}
@Override
public int getOrder() {
return Integer.MAX_VALUE;
};
}為了簡化起見,這個類僅用於記錄一些 TestContext 的信息。
4.1. 使用 @TestExecutionListeners</em/> 註冊自定義監聽器
現在,讓我們在測試類中使用這個監聽器。要做到這一點,我們將通過使用 @TestExecutionListeners</em/> 註解來註冊它:
@RunWith(SpringRunner.class)
@TestExecutionListeners(value = {
CustomTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class
})
@ContextConfiguration(classes = AdditionService.class)
public class AdditionServiceUnitTest {
// ...
}需要注意的是,使用註解將取消所有默認監聽器的註冊。因此,我們明確添加了 DependencyInjectionTestExecutionListener,以便我們在測試類中使用自動裝配。
如果需要使用其他默認監聽器,則必須明確指定它們。但是,我們也可以使用註解的 mergeMode 屬性。
@TestExecutionListeners(
value = { CustomTestExecutionListener.class },
mergeMode = MergeMode.MERGE_WITH_DEFAULTS)這裏,MERGE_WITH_DEFAULTS 表示本地聲明的監聽器應與默認監聽器合併。
現在,當我們運行上述測試時,監聽器將記錄它接收到的每個事件:
[main] INFO o.s.t.c.s.DefaultTestContextBootstrapper - Using TestExecutionListeners:
[com.baeldung.testexecutionlisteners.CustomTestExecutionListener@38364841,
org.springframework.test.context.support.DependencyInjectionTestExecutionListener@28c4711c]
[main] INFO c.b.t.CustomTestExecutionListener - beforeTestClass :
class com.baeldung.testexecutionlisteners.TestExecutionListenersWithoutMergeModeUnitTest
[main] INFO c.b.t.CustomTestExecutionListener - prepareTestInstance :
class com.baeldung.testexecutionlisteners.TestExecutionListenersWithoutMergeModeUnitTest
[main] INFO o.s.c.s.GenericApplicationContext -
Refreshing org.springframework.context.support.GenericApplicationContext@7d68ef40: startup date [XXX];
root of context hierarchy
[main] INFO c.b.t.CustomTestExecutionListener - beforeTestMethod :
public void com.baeldung.testexecutionlisteners.TestExecutionListenersWithoutMergeModeUnitTest
.whenValidNumbersPassed_thenReturnSum()
[main] INFO c.b.t.CustomTestExecutionListener - afterTestMethod :
public void com.baeldung.testexecutionlisteners.TestExecutionListenersWithoutMergeModeUnitTest
.whenValidNumbersPassed_thenReturnSum()
[main] INFO c.b.t.CustomTestExecutionListener - afterTestClass :
class com.baeldung.testexecutionlisteners.TestExecutionListenersWithoutMergeModeUnitTest4.2. 默認 <emTestExecutionListener</em>> 實現的自動發現
使用 <emTestExecutionListener</em>> 註冊監聽器,如果僅在少量測試類中使用,則適用。但是,將其添加到整個測試套件中可能會變得繁瑣。
我們可以通過利用 <emSpringFactoriesLoader</em>> 機制的自動發現 <emTestExecutionListener</em>> 實現來解決這個問題。
<emspring-test</em>> 模塊在它的 <emMETA-INF/spring.factories</em>> 屬性文件中,在 <emorg.springframework.test.context.TestExecutionListener</em> 鍵下聲明瞭所有核心默認監聽器。 類似地,我們可以通過在自己的 <emMETA-INF/spring.factories</em>> 屬性文件中使用上述鍵來註冊我們的自定義監聽器:
org.springframework.test.context.TestExecutionListener=\
com.baeldung.testexecutionlisteners.CustomTestExecutionListener4.3. 使用默認 <em>TestExecutionListener</em> 實現
當 Spring 通過 <em>SpringFactoriesLoader</em> 機制發現默認的 <em>TestExecutionListener</em> 實現時,它將使用 Spring 的 <em>AnnotationAwareOrderComparator</em> 對其進行排序。 這尊重 Spring 的 <em>Ordered</em> 接口和 @Order 註解,用於排序。
請注意,所有由 Spring 提供的默認 <em>TestExecutionListener</em> 實現都實現了 <em>Ordered</em> 接口,並具有適當的值。 因此,我們必須確保我們的自定義 <em>TestExecutionListener</em> 實現已使用正確的順序進行註冊。 因此,我們已在自定義監聽器中實現了 <em>Ordered</em>:
public class CustomTestExecutionListener implements TestExecutionListener, Ordered {
// ...
@Override
public int getOrder() {
return Integer.MAX_VALUE;
};
}但是,我們也可以使用 @Order 註解代替。
5. 結論
在本文中,我們學習瞭如何實現自定義的TestExecutionListener。我們還考察了 Spring 框架提供的默認監聽器。