上下文快取

一旦 TestContext framework 為測試載入了 ApplicationContext(或 WebApplicationContext),該上下文就會被快取並在同一測試套件中宣告相同唯一上下文配置的所有後續測試中重用。要了解快取的工作原理,重要的是理解“唯一”和“測試套件”的含義。

ApplicationContext 可以透過用於載入它的配置引數組合來唯一標識。因此,使用唯一的配置引數組合來生成快取上下文的鍵。TestContext framework 使用以下配置引數構建上下文快取鍵:

  • locations(來自 @ContextConfiguration

  • classes(來自 @ContextConfiguration

  • contextInitializerClasses(來自 @ContextConfiguration

  • contextCustomizers(來自 ContextCustomizerFactory)——這包括 @DynamicPropertySource 方法、bean 覆蓋(例如 @TestBean@MockitoBean@MockitoSpyBean 等)以及 Spring Boot 測試支援的各種功能。

  • contextLoader(來自 @ContextConfiguration

  • parent(來自 @ContextHierarchy

  • activeProfiles(來自 @ActiveProfiles

  • propertySourceDescriptors(來自 @TestPropertySource

  • propertySourceProperties(來自 @TestPropertySource

  • resourceBasePath(來自 @WebAppConfiguration

例如,如果 TestClassA@ContextConfigurationlocations(或 value)屬性指定 {"app-config.xml", "test-config.xml"},則 TestContext framework 會載入相應的 ApplicationContext 並將其儲存在 static 上下文快取中,鍵僅基於這些位置。因此,如果 TestClassB 也為其位置定義了 {"app-config.xml", "test-config.xml"}(透過繼承顯式或隱式定義),但不定義 @WebAppConfiguration、不同的 ContextLoader、不同的活動 profile、不同的上下文初始化器、不同的測試屬性源或不同的父上下文,那麼兩個測試類會共享同一個 ApplicationContext。這意味著載入應用上下文的設定成本僅發生一次(每個測試套件),後續測試執行會快得多。

測試套件和分叉程序

Spring TestContext framework 將應用上下文儲存在靜態快取中。這意味著上下文實際上儲存在 static 變數中。換句話說,如果測試在單獨的程序中執行,則靜態快取會在每次測試執行之間被清除,這有效地停用了快取機制。

為了受益於快取機制,所有測試必須在同一程序或測試套件內執行。這可以透過在 IDE 中將所有測試作為一個組執行來實現。類似地,在使用 Ant、Maven 或 Gradle 等構建框架執行測試時,確保構建框架不會在測試之間分叉非常重要。例如,如果 Maven Surefire 外掛的 forkMode 設定為 alwayspertest,則 TestContext framework 無法在測試類之間快取應用上下文,因此構建過程會執行速度明顯變慢。

上下文快取的大小是有限制的,預設最大大小為 32。每當達到最大大小時,會使用最近最少使用 (LRU) 淘汰策略來淘汰並關閉陳舊的上下文。您可以透過設定名為 spring.test.context.cache.maxSize 的 JVM 系統屬性,從命令列或構建指令碼中配置最大大小。另外,您也可以透過 SpringProperties 機制設定相同的屬性。

由於在給定測試套件中載入大量應用上下文可能會導致該套件花費不必要的時間來執行,因此瞭解已載入和快取的上下文數量通常非常有益。要檢視底層上下文快取的統計資訊,您可以將 org.springframework.test.context.cache 日誌記錄類別的日誌級別設定為 DEBUG

在極少數情況下,如果測試破壞了應用上下文並需要重新載入(例如,透過修改 bean 定義或應用物件的狀態),您可以使用 @DirtiesContext 註解標記您的測試類或測試方法(請參閱 Spring 測試註解 中關於 @DirtiesContext 的討論)。這指示 Spring 從快取中移除上下文並在執行下一個需要相同應用上下文的測試之前重建應用上下文。請注意,對 @DirtiesContext 註解的支援由 DirtiesContextBeforeModesTestExecutionListenerDirtiesContextTestExecutionListener 提供,它們是預設啟用的。

ApplicationContext 生命週期和控制檯日誌記錄

當您需要除錯使用 Spring TestContext Framework 執行的測試時,分析控制檯輸出(即輸出到 SYSOUTSYSERR 流)可能很有用。一些構建工具和 IDE 能夠將控制檯輸出與給定測試關聯起來;但是,一些控制檯輸出無法輕易地與給定測試關聯起來。

關於由 Spring Framework 本身或註冊在 ApplicationContext 中的元件觸發的控制檯日誌記錄,理解由 Spring TestContext Framework 在測試套件內載入的 ApplicationContext 的生命週期非常重要。

測試的 ApplicationContext 通常在準備測試類例項時載入——例如,對測試例項的 @Autowired 欄位執行依賴注入。這意味著在 ApplicationContext 初始化期間觸發的任何控制檯日誌記錄通常無法與單個測試方法關聯。但是,如果上下文根據 @DirtiesContext 語義在執行測試方法之前立即關閉,則將在執行測試方法之前載入上下文的新例項。在後一種情況下,IDE 或構建工具可能會將控制檯日誌記錄與單個測試方法關聯起來。

測試的 ApplicationContext 可以透過以下場景之一關閉。

  • 上下文根據 @DirtiesContext 語義關閉。

  • 上下文由於根據 LRU 淘汰策略自動從快取中淘汰而關閉。

  • 當測試套件的 JVM 終止時,上下文透過 JVM 關閉鉤子關閉。

如果上下文在特定測試方法之後根據 @DirtiesContext 語義關閉,則 IDE 或構建工具可能會將控制檯日誌記錄與單個測試方法關聯起來。如果上下文在測試類之後根據 @DirtiesContext 語義關閉,則在 ApplicationContext 關閉期間觸發的任何控制檯日誌記錄都無法與單個測試方法關聯。類似地,在關閉階段透過 JVM 關閉鉤子觸發的任何控制檯日誌記錄也無法與單個測試方法關聯。

當 Spring ApplicationContext 透過 JVM 關閉鉤子關閉時,在關閉階段執行的回撥會在名為 SpringContextShutdownHook 的執行緒上執行。因此,如果您希望停用透過 JVM 關閉鉤子關閉 ApplicationContext 時觸發的控制檯日誌記錄,您可以在您的日誌記錄框架中註冊一個自定義過濾器,該過濾器允許您忽略該執行緒啟動的任何日誌記錄。