Spring 測試(第一部分)

1.Spring Testing介紹

測試是企業軟件開發的一個組成部分。本章重點討論IoC原則爲單元測試 unit testing增加的價值,以及Spring框架對集成測試 integration testing.的支持帶來的好處。(在企業中對測試的全面處理超出了本參考手冊的範圍。)

2.Unit Testing

與傳統的Java EE開發相比,依賴項注入應該使您的代碼更少地依賴於容器。組成應用程序的pojo應該可以在JUnit或TestNG測試中進行測試,使用新的操作符實例化對象,而不需要Spring或任何其他容器。您可以使用模擬對象mock objects(與其他有價值的測試技術一起使用)來獨立地測試您的代碼。如果您遵循Spring的體系結構建議,那麼代碼庫的清晰分層和組件化將簡化單元測試。例如,您可以通過存根或模仿DAO或存儲庫接口來測試服務層對象,而不需要在運行單元測試時訪問持久數據。

真正的單元測試通常運行得非常快,因爲不需要設置運行時基礎設施。將真正的單元測試作爲開發方法的一部分可以提高您的生產力。您可能不需要測試章節的這一部分來幫助您爲基於ioci的應用程序編寫有效的單元測試。但是,對於某些單元測試場景,Spring框架提供了模擬對象和測試支持類,本章將對此進行描述。

2.1Mock Objects(模擬對象)

Spring包含許多用於模擬的包:

2.1.1 Environment

org.springframework.mock.env 包包含Environment 和PropertySource抽象的模擬實現(參見 Bean Definition ProfilesPropertySource Abstraction)。MockEnvironment和MockPropertySource對於爲依賴於環境特定屬性的代碼開發容器外測試非常有用。

2.1.2 JNDI

org.springframework.mock.jndi包包含jndi SPI的部分實現,您可以使用它爲測試套件或獨立應用程序設置簡單的jndi環境。例如,如果JDBC數據源實例在測試代碼中與在Java EE容器中綁定到相同的JNDI名稱,則可以在測試場景中重用應用程序代碼和配置,而無需修改。

警告:org.springframework.mock.jndi包中的模擬JNDI支持在Spring Framework 5.2開始正式棄用,轉而支持來自第三方(如Simple-JNDI)的完整解決方案。

2.1.3 Servlet API

org.springframework.mock.web包包含一組全面的Servlet API模擬對象,它們對於測試web上下文、控制器和過濾器非常有用。這些模擬對象是針對Spring的Web MVC框架使用的,通常比動態模擬對象(如 EasyMock)或其他Servlet API模擬對象(如 MockObjects)使用起來更方便。

注意:從Spring Framework 5.0開始,org.springframework.mock.web中的模擬對象是基於Servlet 4.0 API的。

Spring MVC測試框架構建在模擬Servlet API對象上,爲Spring MVC提供集成測試框架。參見Spring MVC測試框架 Spring MVC Test Framework

2.1.4  Spring Web Reactive(Spring Web活性)

org.springframework.mock.http.server.reactive包包含ServerHttpRequest和ServerHttpResponse的模擬實現,用於WebFlux應用程序。org.springframework.mock.web.server包包含一個依賴於這些模擬請求和響應對象的模擬ServerWebExchange。

MockServerHttpRequest和MockServerHttpResponse都是從與服務器特定的實現相同的抽象基類擴展而來,並與它們共享行爲。例如,模擬請求創建後是不可變的,但是您可以使用ServerHttpRequest中的mutate()方法來創建修改後的實例。

爲了讓模擬響應正確地實現寫契約並返回寫完成句柄(即,Mono<Void>),它在默認情況下使用cache().then()的通量,它緩衝數據並使其可用於測試中的斷言。應用程序可以設置自定義寫函數(例如,測試無限流)。、

WebTestClient構建在模擬請求和響應的基礎上,爲測試沒有HTTP服務器的WebFlux應用程序提供支持。客戶端還可以用於運行服務器的端到端測試。

2.2 單元測試支持類

Spring包含了許多可以幫助進行單元測試的類。它們分爲兩類:

2.2.1 一般的測試工具

org.springframework.test.util包包含幾個用於單元測試和集成測試的通用實用程序。

ReflectionTestUtils是一組基於反射的實用方法。您可以在測試場景中使用這些方法,在這些場景中,您需要更改常量的值、設置非公共字段、調用非公共setter方法,或者在測試應用程序代碼時調用非公共配置或生命週期回調方法,例如:

  • ORM框架(如JPA和Hibernate)允許私有或受保護的字段訪問,而不是域實體中的屬性的公共setter方法。
  • Spring對註釋(如@Autowired、@Inject和@Resource)的支持,爲私有或受保護的字段、setter方法和配置方法提供依賴注入。
  • 在生命週期回調方法中使用@PostConstruct和@PreDestroy等註釋。

AopTestUtils是一組與aop相關的實用方法。您可以使用這些方法來獲取隱藏在一個或多個Spring代理背後的底層目標對象的引用。例如,如果您使用EasyMock或Mockito之類的庫將bean配置爲動態模擬,並且將模擬包裝在Spring代理中,那麼您可能需要直接訪問底層模擬,以配置對它的期望並執行驗證。有關Spring的核心AOP實用程序,請參見AopUtils和AopProxyUtils。

2.2.2 Spring MVC測試工具

org.springframework.test.web包包含ModelAndViewAssert,您可以將其與JUnit、TestNG或任何其他用於處理Spring MVC ModelAndView對象的單元測試的測試框架組合使用。

注意:單元測試Spring MVC控制器
要將Spring MVC控制器類作爲pojo進行單元測試,可以從Spring的Servlet API模擬中使用ModelAndViewAssert與MockHttpServletRequest、MockHttpSession等進行組合。要將Spring MVC和REST控制器類與Spring MVC的WebApplicationContext配置結合起來進行全面的集成測試,請使用Spring MVC測試框架。

3. 集成測試

本節(本章其餘大部分內容)將介紹Spring應用程序的集成測試。它包括下列主題:

3.1 概述

在不需要部署到應用服務器或連接到其他企業基礎設施的情況下,能夠執行一些集成測試是很重要的。這樣做可以讓您測試以下內容:

  • Spring IoC容器上下文的正確連接。
  • 使用JDBC或ORM工具進行數據訪問。這可能包括SQL語句、Hibernate查詢、JPA實體映射等的正確性。

Spring框架爲Spring -test模塊中的集成測試提供了一流的支持。實際JAR文件的名稱可能包含發佈版本,也可能在長org.springframework中。測試表單,這取決於您從哪裏獲得它(有關依賴項管理的解釋,請參閱相關章節)。這個庫包括org.springframework。測試包,其中包含有價值的類,用於與Spring容器進行集成測試。此測試不依賴於應用服務器或其他部署環境。此類測試的運行速度比單元測試慢,但比等效的Selenium測試或依賴於部署到應用服務器的遠程測試快得多。

單元和集成測試支持以註解驅動的 Spring TestContext Frameworkt的形式提供。TestContext框架不知道實際使用的測試框架,它允許在各種環境中測試的插裝,包括JUnit、TestNG和其他環境。

3.2 集成測試的目標

Spring的集成測試支持有以下主要目標:

  • 在測試之間管理Spring IoC容器緩存。
  • 提供測試裝置實例的依賴項注入。
  • 提供適合集成測試的事務管理。
  • 提供特定於spring的基類,以幫助開發人員編寫集成測試。

接下來的幾節描述每個目標,並提供實現和配置細節的鏈接。

3.2.1 上下文管理和緩存

Spring TestContext框架提供了Spring ApplicationContext實例和WebApplicationContext實例的一致加載,以及這些上下文的緩存。支持已加載上下文的緩存非常重要,因爲啓動時間可能成爲一個問題—不是因爲Spring本身的開銷,而是因爲Spring容器實例化的對象需要一定的時間來實例化。例如,一個有50到100個Hibernate映射文件的項目可能需要10到20秒來加載映射文件,在每個測試裝置中運行每個測試之前的成本會導致整體測試運行變慢,從而降低開發人員的工作效率。

測試類通常聲明XML或Groovy配置元數據的資源位置數組(通常在類路徑中),或用於配置應用程序的組件類數組。這些位置或類與用於生產部署的web.xml或其他配置文件中指定的位置或類相同或類似。

默認情況下,一旦加載,配置的ApplicationContext將被每個測試重用。因此,每個測試套件只產生一次設置成本,並且後續的測試執行要快得多。在此上下文中,術語“測試套件”意味着所有測試都在相同的JVM中運行——例如,所有測試都是從Ant、Maven或Gradle構建中爲給定的項目或模塊運行的。在測試破壞應用程序上下文並需要重新加載(例如,通過修改bean定義或應用程序對象的狀態)的不太可能的情況下,可以將TestContext框架配置爲在執行下一個測試之前重新加載配置並重新構建應用程序上下文。

參見TestContext框架的 Context Management和 Context Caching

3.2.2 Dependency Injection of Test Fixtures

當TestContext框架加載您的應用程序上下文時,它可以選擇使用依賴注入來配置您的測試類實例。這爲使用來自應用程序上下文的預配置bean來設置測試裝置提供了一種方便的機制。這樣做的一個很大的好處是,您可以跨各種測試場景重用應用程序上下文(例如,用於配置spring管理的對象圖、事務代理、數據源實例等),從而避免爲單個測試用例重複複雜的測試夾具設置。

例如,考慮這樣一個場景:我們有一個類(HibernateTitleRepository),它爲一個標題域實體實現了數據訪問邏輯。我們想寫集成測試,測試以下領域:

  • Spring配置:基本上,所有與HibernateTitleRepository bean的配置相關的內容都正確且正確嗎?
  • Hibernate映射文件配置:所有內容都正確映射了嗎?正確的延遲加載設置就位了嗎?
  • HibernateTitleRepository的邏輯:該類的配置實例是否按預期執行?

請參閱 TestContext framework中測試裝置的依賴項注入。

3.2.3 事務管理

在訪問實際數據庫的測試中,一個常見的問題是它們對持久性存儲的狀態的影響。即使在使用開發數據庫時,對狀態的更改也可能影響未來的測試。此外,許多操作(例如插入或修改持久數據)不能在事務外部執行(或驗證)。

TestContext框架解決了這個問題。默認情況下,框架爲每個測試創建並回滾一個事務。您可以編寫假定存在事務的代碼。如果在測試中調用事務代理對象,它們將根據配置的事務語義正確地運行。此外,如果測試方法在爲測試而管理的事務中運行時刪除所選表的內容,則事務默認回滾,數據庫返回到執行測試之前的狀態。通過使用在測試的應用程序上下文中定義的PlatformTransactionManager bean,可以爲測試提供事務支持。

如果您希望提交一個事務(不常見,但是在您希望填充或修改數據庫的特定測試時偶爾有用),您可以告訴TestContext框架使事務提交,而不是使用@Commit註釋進行回滾。

3.2.4 支持集成測試類

Spring TestContext框架提供了幾個抽象的支持類,它們簡化了集成測試的編寫。這些基本測試類提供了良好定義的測試框架掛鉤,以及方便的實例變量和方法,讓您訪問:

  • ApplicationContext,用於執行顯式bean查找或測試上下文的整體狀態。
  • 用於執行SQL語句來查詢數據庫的JdbcTemplate。您可以在執行與數據庫相關的應用程序代碼之前和之後使用這些查詢來確認數據庫狀態,Spring確保這些查詢在與應用程序代碼相同的事務範圍內運行。當與ORM工具一起使用時,一定要避免誤報。

此外,您可能希望使用特定於項目的實例變量和方法創建自己的自定義應用程序級超類。

參見 TestContext framework的支持類。

3.3 JDBC Testing Support

org.springframework.test.jdbc包包含JdbcTestUtils,這是一組與jdbc相關的實用函數,旨在簡化標準的數據庫測試場景。具體來說,JdbcTestUtils提供了以下靜態實用程序方法。

  • countRowsInTable(..):計算給定表中的行數。
  • countRowsInTableWhere(..):使用提供的WHERE子句計算給定表中的行數。
  • deleteFromTables(..):刪除指定表中的所有行。
  • deleteFromTableWhere(..):使用提供的WHERE子句從給定表中刪除行。
  • dropTables(..):刪除指定的表。

注意:AbstractTransactionalJUnit4SpringContextTests和AbstractTransactionalTestNGSpringContextTests提供了方便的方法,可以在JdbcTestUtils中委託上述方法。

spring-jdbc模塊提供了對配置和啓動嵌入式數據庫的支持,您可以在與數據庫交互的集成測試中使用它。有關詳細信息,請參閱嵌入式數據庫支持 Embedded Database Support和使用嵌入式數據庫測試數據訪問邏輯Testing Data Access Logic with an Embedded Database

3.4 註釋

3.4.1 Spring 測試註釋

Spring框架提供了以下一組特定於Spring的註釋,您可以將它們與TestContext框架一起用於單元和集成測試中。有關更多信息,請參見相應的javadoc,包括默認屬性值、屬性別名和其他詳細信息。

Spring的測試註釋包括以下內容:

@BootstrapWith
@BootstrapWith是一個類級註釋,您可以使用它來配置如何引導Spring TestContext框架。具體來說,您可以使用@BootstrapWith來指定自定義TestContextBootstrapper。有關更多詳細信息,請參見啓動TestContext框架一節。

@ContextConfiguration
@ContextConfiguration定義類級元數據,用於確定如何爲集成測試加載和配置ApplicationContext。具體來說,@ContextConfiguration聲明應用程序上下文資源位置或用於加載上下文的組件類。

資源位置通常是位於類路徑中的XML配置文件或Groovy腳本,而組件類通常是@Configuration類。但是,資源位置也可以引用文件系統中的文件和腳本,組件類可以是@Component類、@Service類等等。有關詳細信息,請參見組件類Component Classes

下面的示例顯示了引用XML文件的@ContextConfiguration註釋:

@ContextConfiguration("/test-config.xml") 
class XmlApplicationContextTests {
    // class body...
}

下面的示例顯示了引用類的@ContextConfiguration註釋:

@ContextConfiguration(classes = TestConfig.class) 
class ConfigClassApplicationContextTests {
    // class body...
}

除了聲明資源位置或組件類之外,還可以使用@ContextConfiguration聲明ApplicationContextInitializer類。下面是一個例子:

@ContextConfiguration(initializers = CustomContextIntializer.class) 
class ContextInitializerTests {
    // class body...
}

您也可以選擇使用@ContextConfiguration來聲明ContextLoader策略。但是,請注意,通常不需要顯式地配置加載程序,因爲默認加載程序支持初始化器和資源位置或組件類。

下面的例子同時使用了位置和加載程序:

@ContextConfiguration(locations = "/test-context.xml", loader = CustomContextLoader.class) 
class CustomLoaderXmlApplicationContextTests {
    // class body...
}

注意:@ContextConfiguration支持繼承資源位置或配置類以及由超類聲明的上下文初始化器。

有關更多細節,請參見 Context Management和@ContextConfiguration javadocs。

@WebAppConfiguration
@WebAppConfiguration是一個類級註釋,您可以使用它來聲明爲集成測試加載的ApplicationContext應該是WebApplicationContext。僅在測試類上存在@WebAppConfiguration,就可以確保使用“file:src/main/webapp”作爲web應用程序根路徑(即資源基路徑)的默認值,爲測試加載WebApplicationContext。資源基路徑在後臺用於創建MockServletContext,它作爲測試的WebApplicationContext的ServletContext。

下面的示例展示瞭如何使用@WebAppConfiguration註釋:

@ContextConfiguration
@WebAppConfiguration 
class WebAppTests {
    // class body...
}

要覆蓋默認值,可以使用隱式值屬性指定不同的基本資源路徑。同時支持classpath:和file: resource前綴。如果沒有提供資源前綴,則假定路徑是文件系統資源。下面的例子展示瞭如何指定類路徑資源:

@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources") 
class WebAppTests {
    // class body...
}

注意,@WebAppConfiguration必須與@ContextConfiguration結合使用,可以在單個測試類中使用,也可以在測試類層次結構中使用。更多細節請參見@WebAppConfiguration javadoc。

@ContextHierarchy
@ context是一個類級註釋,用於定義集成測試的ApplicationContext實例的層次結構。@ContextConfiguration應該使用一個或多個@ContextConfiguration實例的列表來聲明,每個實例在上下文層次結構中定義一個級別。下面的例子演示了在單個測試類中使用@ context thier阿其(@ context thier阿其也可以在測試類層次結構中使用):

@ContextHierarchy({
    @ContextConfiguration("/parent-config.xml"),
    @ContextConfiguration("/child-config.xml")
})
class ContextHierarchyTests {
    // class body...
}
@WebAppConfiguration
@ContextHierarchy({
    @ContextConfiguration(classes = AppConfig.class),
    @ContextConfiguration(classes = WebConfig.class)
})
class WebIntegrationTests {
    // class body...
}

如果您需要在測試類層次結構中合併或覆蓋上下文層次結構的給定級別的配置,您必須通過在類層次結構中的每個對應級別上爲@ContextConfiguration中的name屬性提供相同的值來顯式地命名該級別。有關更多示例,請參見上下文層次結構和@Context thierarchjavadoc。

@ActiveProfiles
@ActiveProfiles是一個類級別的註釋,用於在爲集成測試加載ApplicationContext時聲明哪個bean定義配置文件應該是活動的。
下面的例子說明dev配置文件應該是活動的:

@ContextConfiguration
@ActiveProfiles("dev") 
class DeveloperTests {
    // class body...
}

下面的例子表明,開發和集成配置文件都應該是活動的:

@ContextConfiguration
@ActiveProfiles({"dev", "integration"}) 
class DeveloperIntegrationTests {
    // class body...
}

指示開發和集成概要文件應該是活動的。

注意:@ActiveProfiles支持繼承由超類默認聲明的活動bean定義配置文件。還可以通過實現自定義ActiveProfilesResolver並使用@ActiveProfiles的resolver屬性註冊它,以編程方式解析活動bean定義概要。

@TestPropertySource
@TestPropertySource是一個類級別的註釋,您可以使用它來配置屬性文件的位置和內聯屬性,這些屬性將被添加到環境中的PropertySources集合中,以便爲集成測試加載ApplicationContext。

與從操作系統環境或Java系統屬性加載的屬性源以及通過@PropertySource或編程方式由應用程序添加的屬性源相比,測試屬性源具有更高的優先級。因此,可以使用測試屬性源選擇性地覆蓋系統和應用程序屬性源中定義的屬性。此外,內聯屬性具有比從資源位置加載的屬性更高的優先級。

下面的例子演示瞭如何從類路徑聲明屬性文件:

@ContextConfiguration
@TestPropertySource("/test.properties") 
class MyIntegrationTests {
    // class body...
}

下面的例子演示瞭如何聲明內聯屬性:

@ContextConfiguration
@TestPropertySource(properties = { "timezone = GMT", "port: 4242" }) 
class MyIntegrationTests {
    // class body...
}

@DirtiesContext
@DirtiesContext表示在測試執行過程中底層的Spring ApplicationContext已經被污染(也就是說,測試以某種方式修改或破壞了它——例如,通過改變一個單例bean的狀態),應該被關閉。當應用程序上下文被標記爲dirty時,它將從測試框架的緩存中刪除並關閉。因此,對於需要具有相同配置元數據的上下文的任何後續測試,都要重新構建底層Spring容器。

您可以使用@DirtiesContext作爲同一個類或類層次結構中的類級和方法級註釋。在這樣的場景中,根據配置的methodMode和classMode, ApplicationContext在任何這樣的註釋方法之前或之後,以及在當前測試類之前或之後被標記爲dirty。

下面的例子解釋了各種配置場景的上下文何時會被弄髒:

  • 在當前測試類之前,在類模式設置爲BEFORE_CLASS的類上聲明。
@DirtiesContext(classMode = BEFORE_CLASS) 
class FreshContextTests {
    // some tests that require a new Spring container
}
  • 在當前測試類之後,在類模式設置爲AFTER_CLASS(即,默認的類模式)。
@DirtiesContext 
class ContextDirtyingTests {
    // some tests that result in the Spring container being dirtied
}
  • 在當前測試類中的每個測試方法之前,在類上聲明時,將類模式設置爲BEFORE_EACH_TEST_METHOD。
@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) 
class FreshContextTests {
    // some tests that require a new Spring container
}
  • 在當前測試類中的每個測試方法之後,在類上聲明,並將類模式設置爲AFTER_EACH_TEST_METHOD。
@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) 
class ContextDirtyingTests {
    // some tests that result in the Spring container being dirtied
}
  • 在當前測試之前,在方法上聲明時,方法模式設置爲BEFORE_METHOD。
@DirtiesContext(methodMode = BEFORE_METHOD) 
@Test
void testProcessWhichRequiresFreshAppCtx() {
    // some logic that requires a new Spring container
}
  • 在當前測試之後,當方法模式設置爲AFTER_METHOD(即,默認方法模式)。
@DirtiesContext 
@Test
void testProcessWhichDirtiesAppCtx() {
    // some logic that results in the Spring container being dirtied
}

如果您在一個測試中使用@DirtiesContext,而該測試的上下文被配置爲帶有@ context層次結構的上下文層次結構的一部分,那麼您可以使用hierarchyMode標誌來控制上下文緩存被清除的方式。默認情況下,使用窮舉算法來清除上下文緩存,不僅包括當前級別,還包括所有其他上下文層次結構,它們共享當前測試的公共祖先上下文。所有駐留在公共祖先上下文的子層次結構中的ApplicationContext實例將從上下文緩存中刪除並關閉。如果窮舉算法對於特定的用例來說是多餘的,您可以指定更簡單的當前級別算法,如下面的示例所示。

@ContextHierarchy({
    @ContextConfiguration("/parent-config.xml"),
    @ContextConfiguration("/child-config.xml")
})
class BaseTests {
    // class body...
}

class ExtendedTests extends BaseTests {

    @Test
    @DirtiesContext(hierarchyMode = CURRENT_LEVEL) 
    void test() {
        // some logic that results in the child context being dirtied
    }
}

有關窮舉級和當前級算法的詳細信息,請參閱DirtiesContext.HierarchyMode javadoc。

@TestExecutionListeners
@TestExecutionListener爲配置TestExecutionListener實現定義類級元數據,這些實現應該註冊到TestContextManager。通常,@ testexecutionlistener與@ContextConfiguration一起使用。

下面的例子展示瞭如何註冊兩個TestExecutionListener實現:

@ContextConfiguration
@TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) 
class CustomTestExecutionListenerTests {
    // class body...
}

默認情況下,@ testexecutionlistener支持繼承的偵聽器。有關示例和更多細節,請參見javadoc。

@Commit
@Commit表示事務測試方法的事務應該在測試方法完成後提交。您可以使用@Commit作爲@Rollback(false)的直接替代,以便更明確地傳達代碼的意圖。與@Rollback類似,@Commit也可以聲明爲類級或方法級註釋。
下面的例子演示瞭如何使用@Commit註釋:

@Commit 
@Test
void testProcessWithoutRollback() {
    // ...
}

@Rollback
@Rollback指示在測試方法完成後,是否應該回滾事務性測試方法的事務。如果爲真,則回滾事務。否則,事務將被提交(參見@Commit)。即使@Rollback沒有顯式聲明,Spring TestContext框架中的集成測試的回滾也默認爲true。

當聲明爲類級註釋時,@Rollback爲測試類層次結構中的所有測試方法定義默認的回滾語義。當聲明爲方法級註釋時,@Rollback爲特定的測試方法定義回滾語義,可能會覆蓋類級@Rollback或@Commit語義。
下面的示例導致測試方法的結果不能回滾(即,結果被提交到數據庫):

@Rollback(false) 
@Test
void testProcessWithoutRollback() {
    // ...
}

@BeforeTransaction
@BeforeTransaction指出,在事務啓動之前應該運行帶註釋的void方法,因爲使用Spring的@Transactional註釋已經配置爲在事務中運行的測試方法。@BeforeTransaction方法不需要是公共的,可以在基於Java 8的接口默認方法上聲明。

下面的示例展示瞭如何使用@BeforeTransaction註釋:

@BeforeTransaction 
void beforeTransaction() {
    // logic to be executed before a transaction is started
}

@AfterTransaction
@AfterTransaction表示,在事務結束後應該運行帶註釋的void方法,因爲已經使用Spring的@Transactional註釋將測試方法配置爲在事務中運行。@AfterTransaction方法不需要是公共的,可以在基於Java 8的接口默認方法上聲明。

@AfterTransaction
@AfterTransaction表示,在事務結束後應該運行帶註釋的void方法,因爲已經使用Spring的@Transactional註釋將測試方法配置爲在事務中運行。@AfterTransaction方法不需要是公共的,可以在基於Java 8的接口默認方法上聲明。

在事務之後運行此方法。

@Sql
@Sql用於註釋一個測試類或測試方法,以配置在集成測試期間針對給定數據庫運行的SQL腳本。下面的例子展示瞭如何使用它:

@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"}) 
void userTest() {
    // execute code that relies on the test schema and test data
}

@SqlConfig
@SqlConfig定義用於確定如何解析和運行配置了@Sql註釋的SQL腳本的元數據。下面的例子展示瞭如何使用它:

@Test
@Sql(
    scripts = "/test-user-data.sql",
    config = @SqlConfig(commentPrefix = "`", separator = "@@") 
)
void userTest() {
    // execute code that relies on the test data
}

@SqlMergeMode
@SqlMergeMode用於註釋一個測試類或測試方法,以配置方法級別的@Sql聲明是否與類級別的@Sql聲明合併。如果@SqlMergeMode沒有在測試類或測試方法上聲明,則默認情況下將使用覆蓋合併模式。使用覆蓋模式,方法級@Sql聲明將有效地覆蓋類級@Sql聲明。

注意,方法級@SqlMergeMode聲明覆蓋了類級聲明。
下面的示例展示瞭如何在類級別上使用@SqlMergeMode。

@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
@SqlMergeMode(MERGE) 
class UserTests {

    @Test
    @Sql("/user-test-data-001.sql")
    void standardUserProfile() {
        // execute code that relies on test data set 001
    }
}

下面的示例展示瞭如何在方法級別上使用@SqlMergeMode。

@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
class UserTests {

    @Test
    @Sql("/user-test-data-001.sql")
    @SqlMergeMode(MERGE) 
    void standardUserProfile() {
        // execute code that relies on test data set 001
    }
}

@SqlGroup
@SqlGroup是一個聚合多個@Sql註釋的容器註釋。您可以直接使用@SqlGroup來聲明幾個嵌套的@Sql註釋,也可以將它與Java 8對可重複註釋的支持一起使用,其中可以在同一個類或方法上多次聲明@Sql,從而隱式地生成這個容器註釋。下面的例子展示瞭如何聲明一個SQL組:

@Test
@SqlGroup({ 
    @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
    @Sql("/test-user-data.sql")
)}
void userTest() {
    // execute code that uses the test schema and test data
}

3.4.2 標準註釋支持
對於所有配置的Spring TestContext框架,以下注釋都支持標準的語義。注意,這些註釋不是特定於測試的,可以在Spring框架的任何地方使用。

  • @Autowired

  • @Qualifier

  • @Value

  • @Resource (javax.annotation) if JSR-250 is present

  • @ManagedBean (javax.annotation) if JSR-250 is present

  • @Inject (javax.inject) if JSR-330 is present

  • @Named (javax.inject) if JSR-330 is present

  • @PersistenceContext (javax.persistence) if JPA is present

  • @PersistenceUnit (javax.persistence) if JPA is present

  • @Required

  • @Transactional (org.springframework.transaction.annotation) with limited attribute support

注意:JSR-250生命週期註釋
在Spring TestContext框架中,您可以在ApplicationContext中配置的任何應用程序組件上使用帶有標準語義的@PostConstruct和@PreDestroy。然而,這些生命週期註釋在實際的測試類中使用有限。

如果測試類中的方法使用@PostConstruct進行了註釋,那麼該方法將在底層測試框架的任何before方法之前運行(例如,使用JUnit Jupiter的@BeforeEach進行註釋的方法),這適用於測試類中的每個測試方法。另一方面,如果測試類中的方法被@PreDestroy註釋,那麼這個方法就永遠不會運行。因此,在測試類中,我們建議您使用來自底層測試框架的測試生命週期回調,而不是使用@PostConstruct和@PreDestroy。

3.4.3 Spring JUnit 4測試註釋
以下注釋僅在與SpringRunner、Spring的JUnit 4規則或Spring的JUnit 4支持類一起使用時才受支持:

@IfProfileValue
@IfProfileValue指示爲特定的測試環境啓用了帶註釋的測試。如果配置的ProfileValueSource返回與提供的名稱匹配的值,則啓用測試。否則,測試將被禁用並有效地忽略。

您可以在類級、方法級或同時應用@IfProfileValue。對於該類或其子類中的任何方法,@IfProfileValue的類級用法優先於方法級用法。具體地說,如果在類級和方法級都啓用了測試,那麼測試就是啓用的。沒有@IfProfileValue意味着測試是隱式啓用的。這類似於JUnit 4的@Ignore註釋的語義,只是@Ignore的存在總是禁用測試。

下面的例子展示了一個帶有@IfProfileValue註釋的測試:

@IfProfileValue(name="java.vendor", value="Oracle Corporation") 
@Test
public void testProcessWhichRunsOnlyOnOracleJvm() {
    // some logic that should run only on Java VMs from Oracle Corporation
}

或者,您可以配置@IfProfileValue一個值列表(帶有或語義),以在JUnit 4環境中實現對測試組的類似testng的支持。考慮下面的例子:

@IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"}) 
@Test
public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {
    // some logic that should run only for unit and integration test groups
}

@ProfileValueSourceConfiguration
@ProfileValueSourceConfiguration是一個類級註釋,它指定在檢索通過@IfProfileValue註釋配置的配置文件值時使用什麼類型的ProfileValueSource。如果沒有爲測試聲明@ProfileValueSourceConfiguration,則默認使用SystemProfileValueSource。下面的例子演示瞭如何使用@ProfileValueSourceConfiguration:

@ProfileValueSourceConfiguration(CustomProfileValueSource.class) 
public class CustomProfileValueSourceTests {
    // class body...
}

@Timed
@ timing表示帶註釋的測試方法必須在指定的時間段內(以毫秒爲單位)完成執行。如果文本執行時間超過指定的時間段,則測試失敗。
這段時間包括運行測試方法本身,測試的任何重複(參見@Repeat),以及測試夾具的任何設置或拆卸。下面的例子展示瞭如何使用它:

@Timed(millis = 1000) 
public void testProcessWithOneSecondTimeout() {
    // some logic that should not take longer than 1 second to execute
}

Spring的@ timing註釋與JUnit 4的@Test(timeout=…)支持具有不同的語義。具體來說,由於JUnit 4處理測試執行超時的方式(也就是說,通過在單獨的線程中執行測試方法),如果測試花費的時間太長,@Test(timeout=…)會預先導致測試失敗。另一方面,Spring的@ timing不會先發制人地使測試失敗,而是在失敗之前等待測試完成。

@Repeat
@Repeat指示必須重複運行帶註釋的測試方法。要執行的測試方法的次數在註釋中指定。
重複執行的範圍包括測試方法本身的執行,以及測試夾具的任何設置或拆卸。下面的例子演示瞭如何使用@Repeat註釋:

@Repeat(10) 
@Test
public void testProcessRepeatedly() {
    // ...
}

3.4.4 Spring JUnit測試註釋
以下注釋僅在與SpringExtension和JUnit Jupiter(即JUnit 5中的編程模型)一起使用時才受支持:

@SpringJUnitConfig
@SpringJUnitConfig是一個組合了來自JUnit Jupiter的@ExtendWith(SpringExtension.class)和來自Spring TestContext框架的@ContextConfiguration的註釋。它可以在類級別上作爲@ContextConfiguration的替代。關於配置選項,@ContextConfiguration和@SpringJUnitConfig之間的唯一區別是組件類可以用@SpringJUnitConfig中的value屬性聲明。

下面的示例演示瞭如何使用@SpringJUnitConfig註釋來指定配置類:

@SpringJUnitConfig(TestConfig.class) 
class ConfigurationClassJUnitJupiterSpringTests {
    // class body...
}

下面的示例展示瞭如何使用@SpringJUnitConfig註釋來指定配置文件的位置:

@SpringJUnitConfig(locations = "/test-config.xml") 
class XmlJUnitJupiterSpringTests {
    // class body...
}

有關更多細節,請參見上下文管理以及@SpringJUnitConfig和@ContextConfiguration的javadoc。

@SpringJUnitWebConfig
@SpringJUnitWebConfig是一個組合了來自JUnit Jupiter的@ExtendWith(SpringExtension.class)與來自Spring TestContext框架的@ContextConfiguration和@WebAppConfiguration的註釋。您可以在類級別使用它作爲@ContextConfiguration和@WebAppConfiguration的替代。關於配置選項,@ContextConfiguration和@SpringJUnitWebConfig之間的惟一區別是您可以通過使用@SpringJUnitWebConfig中的值屬性來聲明組件類。另外,您只能通過使用@SpringJUnitWebConfig中的resourcePath屬性來覆蓋來自@WebAppConfiguration的值屬性。

下面的例子演示瞭如何使用@SpringJUnitWebConfig註釋來指定配置類:

@SpringJUnitWebConfig(TestConfig.class) 
class ConfigurationClassJUnitJupiterSpringWebTests {
    // class body...
}

下面的例子演示瞭如何使用@SpringJUnitWebConfig註釋來指定配置文件的位置:

@SpringJUnitWebConfig(locations = "/test-config.xml") 
class XmlJUnitJupiterSpringWebTests {
    // class body...
}

有關更多細節,請參見@SpringJUnitWebConfig、@ContextConfiguration和@WebAppConfiguration的上下文管理和javadoc。

@TestConstructor
@TestConstructor是一個類型級別的註釋,用於配置如何從測試的ApplicationContext中的組件自動獲取測試類構造函數的參數。

如果@TestConstructor在測試類上不存在或元存在,則將使用默認的測試構造函數自動連接模式。有關如何更改默認模式的詳細信息,請參閱下面的提示。但是請注意,構造函數上的@Autowired局部聲明優先於@TestConstructor和默認模式。

注意:更改默認的測試構造函數自動連接模式
可以通過設置spring.test.constructor.autowire來更改默認的測試構造函數autowire模式。將JVM系統屬性模式設置爲all。另外,可以通過SpringProperties機制更改默認模式。

如果spring.test.constructor.autowire.mode屬性未設置,測試類構造函數將不會自動自動生成。

從Spring Framework 5.2開始,@TestConstructor只支持與SpringExtension一起使用,以與JUnit Jupiter一起使用。注意,SpringExtension通常會自動爲您註冊——例如,當使用諸如@SpringJUnitConfig和@SpringJUnitWebConfig等註釋,或者使用來自Spring引導測試的各種與測試相關的註釋時。

@EnabledIf
@EnabledIf用於表示已啓用註釋的JUnit Jupiter測試類或測試方法,如果提供的表達式的計算結果爲true,則應運行該方法。具體來說,如果表達式計算爲布爾值。TRUE或一個等於TRUE的字符串(忽略大小寫),測試是啓用的。在類級應用時,默認情況下也會自動啓用該類中的所有測試方法。

表達方式可以是以下任何一種:

  • Spring表達式語言(SpEL)表達式。例如:@EnabledIf (" # {systemProperties [' os.name '] .toLowerCase () .contains (mac)}”)
  • Spring環境中可用屬性的佔位符。例如:@EnabledIf (" $ {smoke.tests.enabled} ")
  • 文本文字。例如:@EnabledIf(“true”)

但是,請注意,不是屬性佔位符的動態解析結果的文本文本的實際價值爲零,因爲@EnabledIf(“false”)與@Disabled和@EnabledIf(“true”)在邏輯上是沒有意義的。

您可以使用@EnabledIf作爲元註釋來創建自定義複合註釋。例如,您可以創建一個自定義的@EnabledOnMac註釋,如下所示:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@EnabledIf(
    expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
    reason = "Enabled on Mac OS"
)
public @interface EnabledOnMac {}

@DisabledIf
@DisabledIf用於表示已註釋的JUnit Jupiter測試類或測試方法已禁用,如果提供的表達式計算結果爲true,則不應執行。具體來說,如果表達式計算爲布爾值。如果一個字符串等於TRUE(忽略大小寫),則禁用該測試。當應用於類級別時,該類中的所有測試方法也會自動禁用。

表達方式可以是以下任何一種:

  • Spring表達式語言(SpEL)表達式。例如:@DisabledIf (" # {systemProperties [' os.name '] .toLowerCase () .contains (mac)}”)
  • Spring環境中可用屬性的佔位符。例如:@DisabledIf (" $ {smoke.tests.disabled} ")
  • 文本文字。例如:@DisabledIf(“true”)

但是,請注意,不是屬性佔位符的動態解析結果的文本文本的實際價值爲零,因爲@DisabledIf(“true”)與@Disabled和@DisabledIf(“false”)在邏輯上是沒有意義的。

您可以使用@DisabledIf作爲元註釋來創建自定義複合註釋。例如,您可以創建一個自定義的@DisabledOnMac註釋,如下所示:

 

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@DisabledIf(
    expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
    reason = "Disabled on Mac OS"
)
public @interface DisabledOnMac {}

3.4.5 對測試的元註釋支持
您可以使用大多數與測試相關的註釋作爲元註釋來創建自定義複合註釋,並減少跨測試套件的配置重複。
您可以使用下面的每一個作爲與TestContext框架相結合的元註釋。

  • @BootstrapWith

  • @ContextConfiguration

  • @ContextHierarchy

  • @ActiveProfiles

  • @TestPropertySource

  • @DirtiesContext

  • @WebAppConfiguration

  • @TestExecutionListeners

  • @Transactional

  • @BeforeTransaction

  • @AfterTransaction

  • @Commit

  • @Rollback

  • @Sql

  • @SqlConfig

  • @SqlMergeMode

  • @SqlGroup

  • @Repeat (only supported on JUnit 4)

  • @Timed (only supported on JUnit 4)

  • @IfProfileValue (only supported on JUnit 4)

  • @ProfileValueSourceConfiguration (only supported on JUnit 4)

  • @SpringJUnitConfig (only supported on JUnit Jupiter)

  • @SpringJUnitWebConfig (only supported on JUnit Jupiter)

  • @TestConstructor (only supported on JUnit Jupiter)

  • @EnabledIf (only supported on JUnit Jupiter)

  • @DisabledIf (only supported on JUnit Jupiter)

舉例:

@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class OrderRepositoryTests { }

@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class UserRepositoryTests { }

如果我們發現我們在基於JUnit 4的測試套件中重複了前面的配置,我們可以通過引入自定義複合註釋來減少重複,該註釋集中了Spring的公共測試配置,如下所示:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }

然後我們可以使用自定義的@TransactionalDevTestConfig註釋來簡化單個基於JUnit 4的測試類的配置,如下所示:

@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class OrderRepositoryTests { }

@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class UserRepositoryTests { }

如果我們編寫使用JUnit Jupiter的測試,我們可以進一步減少代碼重複,因爲JUnit 5中的註釋也可以用作元註釋。考慮下面的例子:

@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }

@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }

如果我們發現我們在基於JUnit木星的測試套件中重複前面的配置,我們可以通過引入自定義組合註釋來減少重複,該註釋集中了Spring和JUnit木星的公共測試配置,如下所示:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }

然後我們可以使用自定義的@TransactionalDevTestConfig註釋來簡化基於JUnit Jupiter的單個測試類的配置,如下所示:

@TransactionalDevTestConfig
class OrderRepositoryTests { }

@TransactionalDevTestConfig
class UserRepositoryTests { }

由於JUnit Jupiter支持使用@Test、@RepeatedTest、ParameterizedTest等作爲元註釋,所以您還可以在測試方法級別創建自定義複合註釋。例如,如果我們希望創建一個組合註釋,將來自JUnit Jupiter的@Test和@Tag註釋與來自Spring的@Transactional註釋結合起來,我們可以創建一個@TransactionalIntegrationTest註釋,如下所示:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Transactional
@Tag("integration-test") // org.junit.jupiter.api.Tag
@Test // org.junit.jupiter.api.Test
public @interface TransactionalIntegrationTest { }

然後我們可以使用自定義的@TransactionalIntegrationTest註釋來簡化基於JUnit Jupiter的測試方法的配置,如下所示:

@TransactionalIntegrationTest
void saveOrder() { }

@TransactionalIntegrationTest
void deleteOrder() { }

有關詳細信息,請參見Spring註釋編程模型wiki頁面 Spring Annotation Programming Model

3.5 Spring和TestContext框架
Spring TestContext框架(位於org.springframework.test.context包)提供了通用的、註釋驅動的單元和集成測試支持,這與所使用的測試框架無關。TestContext框架還非常重視約定而不是配置,您可以通過基於註釋的配置來覆蓋這些合理的默認設置。

除了通用的測試基礎設施之外,TestContext框架還提供了對JUnit 4、JUnit Jupiter(又名JUnit 5)和TestNG的顯式支持。對於JUnit 4和TestNG, Spring提供了抽象的支持類。此外,Spring還爲JUnit 4提供了自定義JUnit運行器和自定義JUnit規則,併爲JUnit Jupiter提供了自定義擴展,允許您編寫所謂的POJO測試類。POJO測試類不需要擴展特定的類層次結構,比如抽象支持類。

下一節將概述TestContext框架的內部結構。如果您只對使用框架感興趣,而對使用您自己的自定義監聽器或自定義加載器擴展框架不感興趣,那麼您可以直接訪問配置(context managementdependency injectiontransaction management)、support classesannotation support部分。

3.5.1 Key抽象

框架的核心由TestContextManager類和TestContext、TestExecutionListener和SmartContextLoader接口組成。爲每個測試類創建一個TestContextManager(例如,用於執行JUnit Jupiter中單個測試類中的所有測試方法)。反過來,TestContextManager管理一個包含當前測試上下文的TestContext。TestContextManager還會隨着測試的進展更新TestContext的狀態,並將其委託給TestExecutionListener實現,後者通過提供依賴項注入、管理事務等等來檢測實際的測試執行。SmartContextLoader負責爲給定的測試類加載ApplicationContext。有關各種實現的更多信息和示例,請參見javadoc和Spring測試套件。

TestContext
TestContext封裝了執行測試的上下文(不知道實際使用的測試框架),併爲它負責的測試實例提供上下文管理和緩存支持。如果需要,TestContext也會委託給SmartContextLoader來加載ApplicationContext。

TestContextManager
TestContextManager是Spring TestContext框架的主要入口點,負責管理單個TestContext,並在定義良好的測試執行點向每個註冊的TestExecutionListener發送事件信號:

  • 在特定測試框架的任何“類之前”或“所有之前”方法之前。
  • 後處理測試實例。
  • 在特定測試框架的任何“之前”或“之前每個”方法之前。
  • 在執行測試方法之前但在測試設置之後。
  • 在測試方法執行之後,但在測試之前將其銷燬。
  • 在特定測試框架的任何“After”或“After each”方法之後。
  • 在特定測試框架的任何“類後”或“畢竟”方法之後。

TestExecutionListener
TestExecutionListener定義了用於響應TestContextManager發佈的測試執行事件的API, TestContextManager是註冊偵聽器的對象。看TestExecutionListener Configuration

上下文加載器
ContextLoader是一個策略接口,用於爲Spring TestContext框架管理的集成測試加載ApplicationContext。您應該實現SmartContextLoader而不是這個接口來提供對組件類、活動bean定義概要文件、測試屬性源、上下文層次結構和WebApplicationContext支持的支持。

SmartContextLoader是ContextLoader接口的擴展,它取代了原來最小的ContextLoader SPI。具體來說,SmartContextLoader可以選擇處理資源位置、組件類或上下文初始化器。此外,SmartContextLoader可以設置活動bean定義配置文件,並在其加載的上下文中測試屬性源。

Spring提供了以下實現:

  • DelegatingSmartContextLoader:兩個默認加載器之一,它代表內部AnnotationConfigContextLoader, GenericXmlContextLoader,或GenericGroovyXmlContextLoader,根據測試類聲明的配置或在默認位置的存在或默認配置類。只有當Groovy位於類路徑上時,才能啓用Groovy支持。
  • WebDelegatingSmartContextLoader:兩個默認加載器之一,它代表內部AnnotationConfigWebContextLoader, GenericXmlWebContextLoader,或GenericGroovyXmlWebContextLoader,根據測試類聲明的配置或在默認位置的存在或默認配置類。只有當@WebAppConfiguration出現在測試類中時纔會使用web ContextLoader。只有當Groovy位於類路徑上時,才能啓用Groovy支持。
  • 註釋configcontextloader:從組件類裝入標準的ApplicationContext。
  • configwebcontextloader:從組件類加載WebApplicationContext。
  • GenericGroovyXmlContextLoader:從資源位置(要麼是Groovy腳本,要麼是XML配置文件)加載標準的ApplicationContext。
  • GenericGroovyXmlWebContextLoader:從資源位置(要麼是Groovy腳本,要麼是XML配置文件)加載WebApplicationContext。
  • GenericXmlContextLoader:從XML資源位置加載標準的ApplicationContext。
  • GenericXmlWebContextLoader:從XML資源位置加載WebApplicationContext。
  • GenericPropertiesContextLoader:從Java屬性文件中加載一個標準的ApplicationContext。

3.5.2 啓動TestContext框架

Spring TestContext框架內部的默認配置對於所有的通用用例來說已經足夠了。但是,有時開發團隊或第三方框架希望更改默認的ContextLoader、實現自定義的TestContext或ContextCache、擴展默認的ContextCustomizerFactory集和TestExecutionListener實現集,等等。對於TestContext框架如何操作的這種低級控制,Spring提供了一種引導策略。

TestContextBootstrapper定義了用於引導TestContext框架的SPI。TestContextManager使用TestContextBootstrapper來加載當前測試的TestExecutionListener實現,並構建它所管理的TestContext。您可以使用@BootstrapWith直接或作爲元註釋爲測試類(或測試類層次結構)配置自定義引導策略。如果沒有使用@BootstrapWith顯式配置bootstrapper,則使用DefaultTestContextBootstrapper或WebTestContextBootstrapper,具體取決於@WebAppConfiguration的存在。

因爲TestContextBootstrapper SPI將來可能會改變(以適應新的需求),所以我們強烈建議實現者不要直接實現這個接口,而是擴展AbstractTestContextBootstrapper或它的一個具體子類。

3.5.3 TestExecutionListener配置
Spring提供了以下默認註冊的TestExecutionListener實現,其順序如下:

  • ServletTestExecutionListener:爲WebApplicationContext配置Servlet API模擬。
  • DirtiesContextBeforeModesTestExecutionListener:處理“before”模式的@DirtiesContext註釋。
  • DependencyInjectionTestExecutionListener:爲測試實例提供依賴注入。
  • DirtiesContextTestExecutionListener:處理“後”模式的@DirtiesContext註釋。
  • TransactionalTestExecutionListener:提供具有默認回滾語義的事務性測試執行。
  • SqlScriptsTestExecutionListener:運行使用@Sql註釋配置的SQL腳本。
  • EventPublishingTestExecutionListener:將測試執行事件發佈到測試的ApplicationContext(參見測試執行事件)。

註冊TestExecutionListener實現
可以使用@TestExecutionListener註釋註冊測試類及其子類的TestExecutionListener實現。有關詳細信息和示例,請參見@ testexecutionlistener的註釋支持和javadoc。

自動發現默認的TestExecutionListener實現
通過使用@TestExecutionListener註冊TestExecutionListener實現適合於在有限的測試場景中使用的自定義監聽器。但是,如果需要在整個測試套件中使用自定義偵聽器,則會變得非常麻煩。這個問題是通過支持通過SpringFactoriesLoader機制自動發現默認TestExecutionListener實現來解決的。

具體來說,spring-test模塊在org.springframework.test.context下聲明瞭所有核心的默認TestExecutionListener實現。TestExecutionListener鍵的元inf /spring。工廠的屬性文件。第三方框架和開發人員可以通過自己的META-INF/spring以相同的方式將自己的TestExecutionListener實現貢獻給默認監聽器列表。工廠的屬性文件。

Ordering TestExecutionListener實現
當TestContext框架通過前面提到的SpringFactoriesLoader機制發現默認的TestExecutionListener實現時,通過使用Spring的AnnotationAwareOrderComparator對實例化的偵聽器進行排序,該方法支持Spring的有序接口和用於排序的@Order註釋。抽象TestExecutionListener和所有由Spring提供的默認TestExecutionListener實現,這些實現使用適當的值排序。因此,第三方框架和開發人員應該通過實現Ordered或聲明@Order來確保他們的默認TestExecutionListener實現以正確的順序註冊。有關分配給每個核心偵聽器的值的詳細信息,請參閱核心默認TestExecutionListener實現的getOrder()方法的javadoc。

合併TestExecutionListener實現
如果自定義的TestExecutionListener是通過@TestExecutionListener註冊的,則默認的偵聽器不會註冊。在大多數常見的測試場景中,這有效地迫使開發人員手動聲明除任何自定義偵聽器之外的所有默認偵聽器。下面的清單演示了這種配置風格:

@ContextConfiguration
@TestExecutionListeners({
    MyCustomTestExecutionListener.class,
    ServletTestExecutionListener.class,
    DirtiesContextBeforeModesTestExecutionListener.class,
    DependencyInjectionTestExecutionListener.class,
    DirtiesContextTestExecutionListener.class,
    TransactionalTestExecutionListener.class,
    SqlScriptsTestExecutionListener.class
})
class MyTest {
    // class body...
}

此方法的挑戰在於,它要求開發人員準確地知道默認情況下注冊了哪些偵聽器。此外,默認監聽器的設置可以隨着版本的不同而變化——例如,SqlScriptsTestExecutionListener是在Spring Framework 4.1中引入的,DirtiesContextBeforeModesTestExecutionListener是在Spring Framework 4.2中引入的。此外,像Spring Boot和Spring Security這樣的第三方框架通過使用前面提到的自動發現機制來註冊它們自己的默認TestExecutionListener實現。

爲了避免必須意識到並重新聲明所有默認監聽器,可以將@ testexecutionlistener的mergeMode屬性設置爲mergeMode . merge_with_defaults。MERGE_WITH_DEFAULTS表示應該將本地聲明的監聽器與默認監聽器合併。合併算法確保從列表中刪除重複的偵聽器,並根據AnnotationAwareOrderComparator的語義對合並後的偵聽器集進行排序,如《排序TestExecutionListener實現》中所述。如果偵聽器實現了Ordered或使用@Order進行了註釋,則它會影響與默認值合併的位置。否則,在合併時將本地聲明的偵聽器追加到默認偵聽器列表。

例如,如果前面的示例配置中的MyCustomTestExecutionListener類訂單價值(例如,500)小於ServletTestExecutionListener的順序(恰好是1000),MyCustomTestExecutionListener可以自動與違約ServletTestExecutionListener前面的列表,和前面的示例可以替換爲以下:

@ContextConfiguration
@TestExecutionListeners(
    listeners = MyCustomTestExecutionListener.class,
    mergeMode = MERGE_WITH_DEFAULTS
)
class MyTest {
    // class body...
}

3.5.4 測試執行的事件
Spring Framework 5.2中引入的EventPublishingTestExecutionListener提供了實現自定義TestExecutionListener的替代方法。測試的ApplicationContext中的組件可以監聽EventPublishingTestExecutionListener發佈的以下事件,每個事件對應於TestExecutionListener API中的一個方法。

  • BeforeTestClassEvent

  • PrepareTestInstanceEvent

  • BeforeTestMethodEvent

  • BeforeTestExecutionEvent

  • AfterTestExecutionEvent

  • AfterTestMethodEvent

  • AfterTestClassEvent

注意:只有當ApplicationContext已經被加載時,纔會發佈這些事件。

這些事件可能由於各種原因被使用,例如重置模擬bean或跟蹤測試執行。使用測試執行事件的一個優點而不是實現一個自定義TestExecutionListener是,測試執行的事件可以被任何Spring bean註冊在測試ApplicationContext,等豆子可能受益直接從依賴注入和ApplicationContext的其他特性。相反,TestExecutionListener不是ApplicationContext中的bean。

爲了監聽測試執行事件,Spring bean可以選擇實現org.springframe .context。ApplicationListener接口。另外,可以使用@EventListener對偵聽器方法進行註釋,並將其配置爲偵聽上面列出的特定事件類型之一(請參閱基於註釋的事件偵聽器)。由於這種方法的流行,Spring提供了以下專用的@EventListener註釋來簡化測試執行事件監聽器的註冊。這些註釋駐留在org.springframework.test.context.event.annotation包。

  • @BeforeTestClass

  • @PrepareTestInstance

  • @BeforeTestMethod

  • @BeforeTestExecution

  • @AfterTestExecution

  • @AfterTestMethod

  • @AfterTestClass

異常處理
默認情況下,如果測試執行事件偵聽器在使用事件時拋出異常,則該異常將傳播到正在使用的底層測試框架(如JUnit或TestNG)。例如,如果使用BeforeTestMethodEvent導致異常,則相應的測試方法將由於異常而失敗。相反,如果異步測試執行事件偵聽器拋出異常,則該異常將不會傳播到底層測試框架。有關異步異常處理的詳細信息,請參考@EventListener的類級javadoc。

異步的聽衆
如果您需要一個特定的測試執行事件監聽器來異步處理事件,您可以使用Spring的常規@Async支持。要了解更多細節,請參考@EventListener的類級別javadoc。

3.5.5 上下文管理
每個TestContext爲它負責的測試實例提供上下文管理和緩存支持。測試實例不會自動接收對已配置的ApplicationContext的訪問。但是,如果測試類實現了ApplicationContext接口,則會將對ApplicationContext的引用提供給測試實例。注意,AbstractJUnit4SpringContextTests和AbstractTestNGSpringContextTests實現了ApplicationContext軟件,因此,它們自動提供了對ApplicationContext的訪問。

注意:@ autowired ApplicationContext
作爲實現applicationcontext - ware接口的替代方法,您可以通過在字段或setter方法上的@Autowired註釋爲您的測試類注入應用程序上下文,如下例所示:

@SpringJUnitConfig
class MyTest {

    @Autowired 
    ApplicationContext applicationContext;

    // class body...
}

類似地,如果您的測試被配置爲加載WebApplicationContext,您可以將web應用程序上下文注入到您的測試中,如下所示:

@SpringJUnitWebConfig 
class MyWebAppTest {

    @Autowired 
    WebApplicationContext wac;

    // class body...
}

通過使用@Autowired實現的依賴注入是由DependencyInjectionTestExecutionListener提供的,它是默認配置的(參見測試裝置的依賴注入)。

使用TestContext框架的測試類不需要擴展任何特定的類或者實現特定的接口來配置它們的應用程序上下文。相反,通過在類級別聲明@ContextConfiguration註釋來實現配置。如果您的測試類沒有顯式地聲明應用程序上下文資源位置或組件類,則配置的ContextLoader將確定如何從默認位置或默認配置類加載上下文。除了上下文資源位置和組件類之外,還可以通過應用程序上下文初始化器配置應用程序上下文。

下面幾節將解釋如何使用Spring的@ContextConfiguration註釋,通過使用XML配置文件、Groovy腳本、組件類(通常是@Configuration類)或上下文初始化器來配置測試ApplicationContext。或者,您可以爲高級用例實現和配置自己的定製SmartContextLoader。

使用XML資源進行上下文配置
要使用XML配置文件爲測試加載ApplicationContext,可以使用@ContextConfiguration註釋測試類,並使用包含XML配置元數據資源位置的數組配置locations屬性。純路徑或相對路徑(例如context.xml)被視爲與定義測試類的包相關的類路徑資源。以斜槓開頭的路徑被視爲絕對類路徑位置(例如,/org/example/config.xml)。表示資源URL的路徑。,以classpath:、file:、http:等爲前綴的路徑)按原樣使用。

@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/app-config.xml" and
// "/test-config.xml" in the root of the classpath
@ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"}) 
class MyTest {
    // class body...
}

@ContextConfiguration通過標準Java值屬性爲locations屬性提供別名。因此,如果您不需要在@ContextConfiguration中聲明額外的屬性,那麼您可以省略location屬性名的聲明,而使用下面示例中演示的簡寫格式來聲明資源位置:

@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-config.xml"}) 
class MyTest {
    // class body...
}

如果您省略了來自@ContextConfiguration註釋的位置和值屬性,TestContext框架將嘗試檢測默認的XML資源位置。具體來說,GenericXmlContextLoader和GenericXmlWebContextLoader根據測試類的名稱檢測默認位置。如果您的類名爲com.example。GenericXmlContextLoader從“classpath:com/example/MyTest-context.xml”加載應用程序上下文。下面的例子演示瞭如何做到這一點:

@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration 
class MyTest {
    // class body...
}

使用Groovy腳本進行上下文配置
要使用使用Groovy Bean定義DSL的Groovy腳本來爲您的測試加載ApplicationContext,您可以使用@ContextConfiguration註釋您的測試類,並使用包含Groovy腳本資源位置的數組來配置location或value屬性。Groovy腳本的資源查找語義與XML配置文件中描述的相同。

注意:

啓用Groovy腳本支持
如果Groovy位於類路徑上,那麼將自動啓用使用Groovy腳本在Spring TestContext框架中加載ApplicationContext的支持。

下面的示例演示如何指定Groovy配置文件:

@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/AppConfig.groovy" and
// "/TestConfig.groovy" in the root of the classpath
@ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"}) 
class MyTest {
    // class body...
}

如果您省略了@ContextConfiguration註釋中的位置和值屬性,TestContext框架將嘗試檢測一個默認的Groovy腳本。具體來說,GenericGroovyXmlContextLoader和GenericGroovyXmlWebContextLoader根據測試類的名稱檢測默認位置。如果您的類名爲com.example。Groovy上下文加載器從“classpath:com/example/MyTestContext.groovy”加載應用程序上下文。下面的例子展示瞭如何使用默認值:

@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTestContext.groovy"
@ContextConfiguration 
class MyTest {
    // class body...
}

注意:同時聲明XML配置和Groovy腳本
您可以使用@ContextConfiguration的location或value屬性同時聲明XML配置文件和Groovy腳本。如果到配置資源位置的路徑以.xml結束,則使用XmlBeanDefinitionReader加載它。否則,使用GroovyBeanDefinitionReader加載它。

下面的清單展示瞭如何在集成測試中結合兩者:

@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "/app-config.xml" and "/TestConfig.groovy"
@ContextConfiguration({ "/app-config.xml", "/TestConfig.groovy" })
class MyTest {
    // class body...
}

帶有組件類的上下文配置
要使用組件類爲測試加載ApplicationContext(請參閱基於java的容器配置),可以使用@ContextConfiguration註釋測試類,並使用包含對組件類的引用的數組配置classes屬性。下面的例子演示瞭如何做到這一點:

@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from AppConfig and TestConfig
@ContextConfiguration(classes = {AppConfig.class, TestConfig.class}) 
class MyTest {
    // class body...
}

注意:

組件類
“組件類”一詞可指下列任何一種:

  • 一個帶有@Configuration註釋的類。
  • 一個組件(也就是說,一個用@Component, @Service, @Repository,或者其他原型註釋註釋的類)。
  • 一個JSR-330兼容的類,用javax註釋。注入註解。
  • 包含@ bean -方法的任何類。
  • 打算註冊爲Spring組件的任何其他類(即,潛在地利用單個構造函數的自動自動裝配,而不使用Spring註解。

有關組件類的配置和語義的更多信息,請參閱@Configuration和@Bean的javadoc,特別注意對@Bean Lite模式的討論。

如果您省略了@ContextConfiguration註釋中的classes屬性,TestContext框架將嘗試檢測默認配置類的存在。具體來說,AnnotationConfigContextLoader和AnnotationConfigWebContextLoader檢測測試類中滿足配置類實現需求的所有靜態嵌套類,如@Configuration javadoc中指定的那樣。注意,配置類的名稱是任意的。此外,如果需要,一個測試類可以包含多個靜態嵌套配置類。在下面的示例中,OrderServiceTest類聲明瞭一個名爲Config的靜態嵌套配置類,該配置類自動用於爲測試類加載ApplicationContext:

@SpringJUnitConfig 
// ApplicationContext will be loaded from the
// static nested Config class
class OrderServiceTest {

    @Configuration
    static class Config {

        // this bean will be injected into the OrderServiceTest class
        @Bean
        OrderService orderService() {
            OrderService orderService = new OrderServiceImpl();
            // set properties, etc.
            return orderService;
        }
    }

    @Autowired
    OrderService orderService;

    @Test
    void testOrderService() {
        // test the orderService
    }

}

從嵌套配置類加載配置信息。

混合XML、Groovy腳本和組件類
有時可能需要混合XML配置文件、Groovy腳本和組件類(通常是@Configuration類)來爲測試配置ApplicationContext。例如,如果您在生產環境中使用XML配置,您可能會決定使用@Configuration類來爲您的測試配置特定的spring託管組件,反之亦然。

此外,一些第三方框架(如Spring Boot)爲同時從不同類型的資源加載ApplicationContext提供了一流的支持(例如,XML配置文件、Groovy腳本和@Configuration類)。從歷史上看,Spring框架並不支持這種標準部署。因此,Spring框架在Spring -test模塊中提供的大多數SmartContextLoader實現只支持每個測試上下文的一種資源類型。但是,這並不意味着您不能同時使用這兩種方法。通用規則的一個例外是GenericGroovyXmlContextLoader和GenericGroovyXmlWebContextLoader同時支持XML配置文件和Groovy腳本。此外,第三方框架可能選擇通過@ContextConfiguration來支持位置和類的聲明,並且,使用TestContext框架中的標準測試支持,您有以下選項。

如果您想使用資源位置(例如,XML或Groovy)和@Configuration類來配置您的測試,那麼您必須選擇一個作爲入口點,這個入口點必須包含或導入另一個入口點。例如,在XML或Groovy腳本中,您可以通過使用組件掃描或將它們定義爲普通的Spring bean來包含@Configuration類,而在@Configuration類中,您可以使用@ImportResource來導入XML配置文件或Groovy腳本。注意,這個行爲語義上等價於你如何配置您的應用程序在生產:在生產配置中,您定義一組XML或Groovy的資源位置或一組@ configuration類生產加載ApplicationContext,但你仍然可以自由包括或導入其他類型的配置。

使用上下文初始化器的上下文配置
要通過使用上下文初始化器來爲您的測試配置一個ApplicationContext,可以使用@ContextConfiguration來註釋您的測試類,並使用一個包含實現ApplicationContextInitializer的類引用的數組來配置初始化器屬性。聲明的上下文初始化器然後用於初始化爲您的測試加載的ConfigurableApplicationContext。注意,每個聲明的初始化器支持的具體的ConfigurableApplicationContext類型必須與使用中的SmartContextLoader(通常是GenericApplicationContext)創建的ApplicationContext類型兼容。此外,調用初始化器的順序取決於它們是實現Spring的有序接口,還是使用Spring的@Order註釋,還是使用標準的@Priority註釋。下面的例子展示瞭如何使用初始化:

@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from TestConfig
// and initialized by TestAppCtxInitializer
@ContextConfiguration(
    classes = TestConfig.class,
    initializers = TestAppCtxInitializer.class) 
class MyTest {
    // class body...
}

你也可以省略XML配置文件的聲明,Groovy腳本,或組件類@ContextConfiguration完全而宣佈只有ApplicationContextInitializer類,負責註冊bean中——例如,通過編程的方式加載bean定義從XML文件或配置類。下面的例子演示瞭如何做到這一點:

@ExtendWith(SpringExtension.class)
// ApplicationContext will be initialized by EntireAppInitializer
// which presumably registers beans in the context
@ContextConfiguration(initializers = EntireAppInitializer.class) 
class MyTest {
    // class body...
}

上下文配置繼承
@ContextConfiguration支持布爾繼承位置和繼承初始化器屬性,這些屬性表示是否應該繼承由超類聲明的資源位置或組件類和上下文初始化器。兩個標誌的默認值都爲true。這意味着測試類繼承了資源位置或組件類以及由任何超類聲明的上下文初始化器。具體來說,測試類的資源位置或組件類被附加到由超類聲明的資源位置或帶註釋的類的列表中。類似地,給定測試類的初始化器被添加到測試超類定義的初始化器集合中。因此,子類可以選擇擴展資源位置、組件類或上下文初始化器。

如果@ContextConfiguration中的inheritLocations或inheritinitialalizer屬性設置爲false,則測試類的資源位置或組件類和上下文初始化器將分別被設置爲shadow並有效地替換超類定義的配置。

在下一個使用XML資源位置的示例中,ExtendedTest的ApplicationContext是從base-config.xml和exten- config加載的。xml,按照這個順序。因此,在extended-config.xml中定義的bean可以覆蓋(即替換)那些在base-config.xml中定義的bean。下面的示例展示了一個類如何擴展另一個類,並同時使用自己的配置文件和超類的配置文件:

@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/base-config.xml"
// in the root of the classpath
@ContextConfiguration("/base-config.xml") 
class BaseTest {
    // class body...
}

// ApplicationContext will be loaded from "/base-config.xml" and
// "/extended-config.xml" in the root of the classpath
@ContextConfiguration("/extended-config.xml") 
class ExtendedTest extends BaseTest {
    // class body...
}

類似地,在下一個使用組件類的示例中,ExtendedTest的ApplicationContext是按這個順序從BaseConfig和ExtendedConfig類加載的。因此,ExtendedConfig中定義的bean可以覆蓋(即替換)那些在BaseConfig中定義的bean。下面的例子展示了一個類如何擴展另一個類,並同時使用它自己的配置類和超類的配置類:

// ApplicationContext will be loaded from BaseConfig
@SpringJUnitConfig(BaseConfig.class) 
class BaseTest {
    // class body...
}

// ApplicationContext will be loaded from BaseConfig and ExtendedConfig
@SpringJUnitConfig(ExtendedConfig.class) 
class ExtendedTest extends BaseTest {
    // class body...
}

在下一個使用上下文初始化器的示例中,ExtendedTest的ApplicationContext是通過使用BaseInitializer和ExtendedInitializer進行初始化的。但是,請注意,調用初始化器的順序取決於它們是實現Spring的有序接口,還是使用Spring的@Order註釋,還是使用標準的@Priority註釋。下面的示例展示了一個類如何擴展另一個類,並同時使用自己的初始化器和超類的初始化器:

// ApplicationContext will be initialized by BaseInitializer
@SpringJUnitConfig(initializers = BaseInitializer.class) 
class BaseTest {
    // class body...
}

// ApplicationContext will be initialized by BaseInitializer
// and ExtendedInitializer
@SpringJUnitConfig(initializers = ExtendedInitializer.class) 
class ExtendedTest extends BaseTest {
    // class body...
}

環境配置文件的上下文配置
Spring框架對環境和概要文件的概念提供了一流的支持(又稱爲“bean定義概要文件”),可以配置集成測試來激活各種測試場景的特定bean定義概要文件。這是通過使用@ActiveProfiles註釋一個測試類並提供一個概要文件列表來實現的,這些概要文件應該在爲測試加載ApplicationContext時被激活。

注意:您可以在SmartContextLoader SPI的任何實現中使用@ActiveProfiles,但是舊的ContextLoader SPI的實現不支持@ActiveProfiles。

考慮XML配置和@Configuration類的兩個示例:

<!-- app-config.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <bean id="transferService"
            class="com.bank.service.internal.DefaultTransferService">
        <constructor-arg ref="accountRepository"/>
        <constructor-arg ref="feePolicy"/>
    </bean>

    <bean id="accountRepository"
            class="com.bank.repository.internal.JdbcAccountRepository">
        <constructor-arg ref="dataSource"/>
    </bean>

    <bean id="feePolicy"
        class="com.bank.service.internal.ZeroFeePolicy"/>

    <beans profile="dev">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script
                location="classpath:com/bank/config/sql/schema.sql"/>
            <jdbc:script
                location="classpath:com/bank/config/sql/test-data.sql"/>
        </jdbc:embedded-database>
    </beans>

    <beans profile="production">
        <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
    </beans>

    <beans profile="default">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script
                location="classpath:com/bank/config/sql/schema.sql"/>
        </jdbc:embedded-database>
    </beans>

</beans>
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {

    @Autowired
    TransferService transferService;

    @Test
    void testTransferService() {
        // test the transferService
    }
}

當TransferServiceTest運行時,它的ApplicationContext從類路徑根目錄中的app-config.xml配置文件中加載。如果你檢查app-config。在xml中,您可以看到accountRepository bean依賴於數據源bean。但是,dataSource沒有定義爲頂級bean。相反,數據源被定義了三次:在生產配置文件中,在開發配置文件中,在默認配置文件中。

通過使用@ActiveProfiles(“dev”)註釋TransferServiceTest,我們指示Spring TestContext框架用設置爲{“dev”}的活動配置文件加載ApplicationContext。結果,一個嵌入式數據庫被創建並使用測試數據填充,accountRepository bean被連接到開發數據源的引用。這可能就是我們在集成測試中想要的。

有時將bean分配給默認配置文件是很有用的。只有在沒有特定激活其他配置文件時,纔會包括默認配置文件中的bean。您可以使用它來定義在應用程序的默認狀態下使用的“回退”bean。例如,您可以顯式地爲開發和生產配置文件提供一個數據源,但是在這些配置文件都不活動時,將內存中的數據源定義爲默認數據源。

下面的代碼清單演示瞭如何使用@Configuration類而不是XML實現相同的配置和集成測試:

@Configuration
@Profile("dev")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}
@Configuration
@Profile("production")
public class JndiDataConfig {

    @Bean(destroyMethod="")
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}
@Configuration
@Profile("default")
public class DefaultDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .build();
    }
}
@Configuration
public class TransferServiceConfig {

    @Autowired DataSource dataSource;

    @Bean
    public TransferService transferService() {
        return new DefaultTransferService(accountRepository(), feePolicy());
    }

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public FeePolicy feePolicy() {
        return new ZeroFeePolicy();
    }
}
@SpringJUnitConfig({
        TransferServiceConfig.class,
        StandaloneDataConfig.class,
        JndiDataConfig.class,
        DefaultDataConfig.class})
@ActiveProfiles("dev")
class TransferServiceTest {

    @Autowired
    TransferService transferService;

    @Test
    void testTransferService() {
        // test the transferService
    }
}

在這個變體中,我們將XML配置分成四個獨立的@Configuration類:

  • TransferServiceConfig:使用@Autowired來通過依賴注入獲取數據源。
  • 定義適合開發人員測試的嵌入式數據庫的數據源。
  • JndiDataConfig:定義在生產環境中從JNDI檢索的數據源。
  • DefaultDataConfig:爲默認的嵌入式數據庫定義一個數據源,以防沒有活動的配置文件。

與基於xml的配置示例一樣,我們仍然使用@ActiveProfiles(“dev”)來註釋TransferServiceTest,但是這次我們使用@ContextConfiguration註釋來指定所有四個配置類。測試類本身的主體保持完全不變。

通常情況下,在一個給定的項目中,一組概要文件被跨多個測試類使用。因此,爲了避免重複聲明@ActiveProfiles註釋,您可以在基類上聲明一次@ActiveProfiles,子類會自動從基類繼承@ActiveProfiles配置。在下面的例子中,@ActiveProfiles的聲明(以及其他註釋)已經被轉移到一個抽象超類AbstractIntegrationTest:

@SpringJUnitConfig({
        TransferServiceConfig.class,
        StandaloneDataConfig.class,
        JndiDataConfig.class,
        DefaultDataConfig.class})
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
// "dev" profile inherited from superclass
class TransferServiceTest extends AbstractIntegrationTest {

    @Autowired
    TransferService transferService;

    @Test
    void testTransferService() {
        // test the transferService
    }
}

@ActiveProfiles還支持一個inheritProfiles屬性,可以用來禁用活動概要文件的繼承,如下面的例子所示:

// "dev" profile overridden with "production"
@ActiveProfiles(profiles = "production", inheritProfiles = false)
class ProductionTransferServiceTest extends AbstractIntegrationTest {
    // test body
}

此外,有時有必要以編程方式而不是聲明的方式來解析測試的活動配置文件——例如,基於:

  • 當前操作系統。
  • 測試是否在持續集成構建服務器上執行。
  • 某些環境變量的存在。
  • 自定義類級註釋的存在。
  • 其他問題。

要以編程方式解析活動bean定義配置文件,可以實現自定義ActiveProfilesResolver,並使用@ActiveProfiles的resolver屬性註冊它。有關更多信息,請參見相應的javadoc。下面的例子演示瞭如何實現和註冊一個自定義的OperatingSystemActiveProfilesResolver:

// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
        resolver = OperatingSystemActiveProfilesResolver.class,
        inheritProfiles = false)
class TransferServiceTest extends AbstractIntegrationTest {
    // test body
}
public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver {

    @Override
    public String[] resolve(Class<?> testClass) {
        String profile = ...;
        // determine the value of profile based on the operating system
        return new String[] {profile};
    }
}

帶有測試屬性源的上下文配置
Spring框架對具有屬性源層次結構的環境概念提供了一流的支持,您可以使用特定於測試的屬性源配置集成測試。與@Configuration類上使用的@PropertySource註釋不同,您可以在測試類上聲明@TestPropertySource註釋,以聲明測試屬性文件或內聯屬性的資源位置。這些測試屬性源被添加到環境中的屬性源集合中,這些屬性源是爲帶註釋的集成測試加載的ApplicationContext而準備的。

注意:您可以在SmartContextLoader SPI的任何實現中使用@TestPropertySource,但是舊的ContextLoader SPI的實現不支持@TestPropertySource。
SmartContextLoader的實現通過MergedContextConfiguration中的getPropertySourceLocations()和getPropertySourceProperties()方法獲得對合並的測試屬性源值的訪問。

聲明測試屬性源
您可以通過使用@TestPropertySource的location或值屬性來配置測試屬性文件。
同時支持傳統的和基於xml的屬性文件格式——例如,“classpath:/com/example/test”。屬性”或“文件:/ / /道路/ / file.xml”。
每個路徑都被解釋爲一個Spring資源。普通路徑(例如,“test.properties”)被視爲與定義測試類的包相關的類路徑資源。以斜槓開頭的路徑被視爲絕對類路徑資源(例如:“/org/example/test.xml”)。使用指定的資源協議加載引用URL的路徑(例如,以classpath:、file:或http:爲前綴的路徑)。不允許使用資源位置通配符(例如*/.properties):每個位置必須精確計算一個.properties或.xml資源。

下面的例子使用了一個測試屬性文件:

@ContextConfiguration
@TestPropertySource("/test.properties") 
class MyIntegrationTests {
    // class body...
}

可以使用@TestPropertySource的properties屬性以鍵-值對的形式配置內聯屬性,如下面的示例所示。所有鍵值對都作爲具有最高優先級的單個測試屬性源添加到封閉環境中。
鍵-值對支持的語法與Java屬性文件中定義的條目語法相同:

  • key=value

  • key:value

  • key value

下面的例子設置了兩個內聯屬性:

@ContextConfiguration
@TestPropertySource(properties = {"timezone = GMT", "port: 4242"}) 
class MyIntegrationTests {
    // class body...
}

注意:從Spring Framework 5.2開始,@TestPropertySource可以用作可重複的註釋。這意味着您可以在單個測試類上使用@TestPropertySource的多個聲明,並且使用後面的@TestPropertySource註釋中的位置和屬性覆蓋前面的@TestPropertySource註釋中的位置和屬性。

此外,您可以在一個測試類上聲明多個複合註釋,每個複合註釋都使用@TestPropertySource進行元註釋,並且所有這些@TestPropertySource聲明都將有助於您的測試屬性源。

直接表示@TestPropertySource註釋總是優先於元表示@TestPropertySource註釋。換句話說,直接來自@TestPropertySource註釋的位置和屬性將覆蓋作爲元註釋使用的@TestPropertySource註釋的位置和屬性。

默認屬性文件檢測
如果@TestPropertySource被聲明爲空註釋(也就是說,沒有位置或屬性屬性的顯式值),則嘗試檢測相對於聲明註釋的類的默認屬性文件。例如,如果帶註釋的測試類是com.example。對應的默認屬性文件是classpath:com/example/MyTest.properties。如果無法檢測到默認值,則拋出IllegalStateException。

優先級
與從操作系統環境、Java系統屬性或應用程序通過使用@PropertySource或編程方式聲明添加的屬性源加載的屬性源相比,測試屬性源具有更高的優先級。因此,可以使用測試屬性源選擇性地覆蓋系統和應用程序屬性源中定義的屬性。此外,內聯屬性具有比從資源位置加載的屬性更高的優先級。

在下一個示例中,時區和端口屬性以及“/test”中定義的任何屬性。屬性“覆蓋在系統和應用程序屬性源中定義的任何同名屬性。此外,如果“/test”。屬性”文件定義時區和端口屬性的條目,這些條目由使用properties屬性聲明的內聯屬性覆蓋。下面的例子展示瞭如何在文件和內聯中指定屬性:

@ContextConfiguration
@TestPropertySource(
    locations = "/test.properties",
    properties = {"timezone = GMT", "port: 4242"}
)
class MyIntegrationTests {
    // class body...
}

繼承和重寫測試屬性源
@TestPropertySource支持boolean inheritLocations和inheritProperties屬性,它們表示屬性文件的資源位置和超類聲明的內聯屬性是否應該繼承。兩個標誌的默認值都爲true。這意味着測試類繼承由任何超類聲明的位置和內聯屬性。特別是,位置和測試類的內聯屬性被附加到由超類聲明的位置和內聯屬性。因此,子類可以選擇擴展位置和內聯屬性。注意,後面出現的屬性會對前面出現的同名屬性進行隱藏(即覆蓋)。此外,上述優先規則也適用於繼承的測試屬性源。

如果@TestPropertySource中的inheritLocations或inheritProperties屬性設置爲false,則測試類的locations或inlinlinproperties將分別隱藏起來,從而有效地替換超類定義的配置。

在下一個示例中,僅使用base加載BaseTest的ApplicationContext。屬性文件作爲測試屬性源。相反,ExtendedTest的ApplicationContext是通過使用基來加載的。屬性和擴展。屬性文件作爲測試屬性源位置。下面的例子展示瞭如何定義屬性在子類和它的超類使用屬性文件:

@TestPropertySource("base.properties")
@ContextConfiguration
class BaseTest {
    // ...
}

@TestPropertySource("extended.properties")
@ContextConfiguration
class ExtendedTest extends BaseTest {
    // ...
}

在下一個示例中,僅使用內聯的key1屬性加載BaseTest的ApplicationContext。相反,ExtendedTest的ApplicationContext是通過使用內聯的key1和key2屬性加載的。下面的例子演示瞭如何使用內聯屬性來定義子類及其超類中的屬性:

@TestPropertySource(properties = "key1 = value1")
@ContextConfiguration
class BaseTest {
    // ...
}

@TestPropertySource(properties = "key2 = value2")
@ContextConfiguration
class ExtendedTest extends BaseTest {
    // ...
}

加載WebApplicationContext
要指示TestContext框架加載WebApplicationContext而不是標準的ApplicationContext,您可以使用@WebAppConfiguration註釋相應的測試類。

測試類中@WebAppConfiguration的出現指示TestContext框架(TCF)應該爲集成測試加載WebApplicationContext (WAC)。在後臺,TCF確保創建了MockServletContext並將其提供給測試的WAC。默認情況下,MockServletContext的基本資源路徑設置爲src/main/webapp。這可以解釋爲相對於JVM根的路徑(通常是到項目的路徑)。如果您熟悉Maven項目中web應用程序的目錄結構,那麼您就知道src/main/webapp是WAR根目錄的默認位置。如果需要覆蓋此默認值,可以提供@WebAppConfiguration註釋的替代路徑(例如,@WebAppConfiguration(“src/test/webapp”))。如果希望從類路徑而不是文件系統引用基本資源路徑,可以使用Spring的classpath: prefix

請注意,Spring對WebApplicationContext實現的測試支持與對標準ApplicationContext實現的支持不相上下。在使用WebApplicationContext進行測試時,您可以使用@ContextConfiguration聲明XML配置文件、Groovy腳本或@Configuration類。您還可以自由地使用任何其他的測試註釋,比如@ActiveProfiles、@ testexecutionlistener、@Sql、@Rollback等。

本節中的其餘示例展示了加載WebApplicationContext的各種配置選項。下面的例子展示了TestContext框架對約定優於配置的支持:

@ExtendWith(SpringExtension.class)

// defaults to "file:src/main/webapp"
@WebAppConfiguration

// detects "WacTests-context.xml" in the same package
// or static nested @Configuration classes
@ContextConfiguration
class WacTests {
    //...
}

如果您使用@WebAppConfiguration註釋一個測試類,而沒有指定資源基路徑,那麼資源路徑實際上默認爲file:src/main/webapp。類似地,如果您聲明@ContextConfiguration而沒有指定資源位置、組件類或上下文初始化器,Spring將嘗試使用約定(即WacTests-context.xml與WacTests類或靜態嵌套的@Configuration類位於同一個包中)來檢測您的配置是否存在。

下面的示例顯示瞭如何使用@WebAppConfiguration顯式地聲明資源基路徑,以及使用@ContextConfiguration顯式地聲明XML資源位置:

@ExtendWith(SpringExtension.class)

// file system resource
@WebAppConfiguration("webapp")

// classpath resource
@ContextConfiguration("/spring/test-servlet-config.xml")
class WacTests {
    //...
}

這裏需要注意的重要一點是,使用這兩個註釋的路徑具有不同的語義。默認情況下,@WebAppConfiguration資源路徑是基於文件系統的,而@ContextConfiguration資源位置是基於類路徑的。

下面的例子表明,我們可以通過指定Spring資源前綴來覆蓋這兩個註釋的默認資源語義:

@ExtendWith(SpringExtension.class)

// classpath resource
@WebAppConfiguration("classpath:test-web-resources")

// file system resource
@ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml")
class WacTests {
    //...
}

將本例中的註釋與前面的示例進行對比。
使用Web模擬
爲了提供全面的web測試支持,TestContext框架有一個默認啓用的ServletTestExecutionListener。當測試WebApplicationContext,這TestExecutionListener設置默認線程局部狀態使用Spring Web的RequestContextHolder在每個測試方法之前,創建一個MockHttpServletRequest, MockHttpServletResponse, ServletWebRequest配置@WebAppConfiguration基於基礎資源路徑。ServletTestExecutionListener還確保可以將MockHttpServletResponse和ServletWebRequest注入到測試實例中,並且一旦測試完成,它就清理線程本地狀態。

一旦您爲您的測試加載了WebApplicationContext,您可能會發現您需要與web模擬進行交互——例如,設置您的測試裝置或者在調用您的web組件後執行斷言。下面的示例顯示了哪些模擬可以自動裝配到您的測試實例中。注意,WebApplicationContext和MockServletContext都緩存在測試套件中,而其他模擬則由ServletTestExecutionListener根據測試方法進行管理。

@SpringJUnitWebConfig
class WacTests {

    @Autowired
    WebApplicationContext wac; // cached

    @Autowired
    MockServletContext servletContext; // cached

    @Autowired
    MockHttpSession session;

    @Autowired
    MockHttpServletRequest request;

    @Autowired
    MockHttpServletResponse response;

    @Autowired
    ServletWebRequest webRequest;

    //...
}

上下文緩存
一旦TestContext框架爲一個測試加載了一個ApplicationContext(或WebApplicationContext),這個上下文就會被緩存,並被所有後續的在同一個測試套件中聲明相同的唯一上下文配置的測試重用。爲了理解緩存是如何工作的,理解“惟一”和“測試套件”的含義是很重要的。

ApplicationContext可以通過用於加載它的配置參數組合唯一地標識。因此,使用配置參數的唯一組合來生成用於緩存上下文的鍵。TestContext框架使用以下配置參數來構建上下文緩存鍵:

  • locations (from @ContextConfiguration)

  • classes (from @ContextConfiguration)

  • contextInitializerClasses (from @ContextConfiguration)

  • contextCustomizers (from ContextCustomizerFactory)

  • contextLoader (from @ContextConfiguration)

  • parent (from @ContextHierarchy)

  • activeProfiles (from @ActiveProfiles)

  • propertySourceLocations (from @TestPropertySource)

  • propertySourceProperties (from @TestPropertySource)

  • resourceBasePath (from @WebAppConfiguration)

例如,如果TestClassA指定{"app-config.xml”、“test-config.xml" 對於@ContextConfiguration的locations(或值)屬性,TestContext框架加載相應的ApplicationContext,並將其存儲在靜態上下文緩存中,該緩存的鍵僅基於這些位置。因此,如果TestClassB也定義了{"app-config.xml", "test-config.xml" 對於其位置(通過繼承顯式或隱式地),但是不定義@WebAppConfiguration、不同的ContextLoader、不同的活動配置文件、不同的上下文初始化器、不同的測試屬性源或不同的父上下文,那麼兩個測試類共享相同的ApplicationContext。這意味着加載應用程序上下文的設置成本只發生一次(每個測試套件),並且後續的測試執行要快得多。

注意:

測試套件和分叉的流程
Spring TestContext框架將應用程序上下文存儲在靜態緩存中。這意味着上下文實際上存儲在一個靜態變量中。換句話說,如果測試在單獨的進程中執行,那麼在每個測試執行之間將清除靜態緩存,這將有效地禁用緩存機制。

爲了從緩存機制中獲益,所有測試必須在相同的流程或測試套件中運行。這可以通過在IDE中作爲一個組執行所有測試來實現。類似地,在使用構建框架(如Ant、Maven或Gradle)執行測試時,務必確保構建框架不會在測試之間轉移。例如,如果Maven Surefire插件的forkMode被設置爲always或pertest,那麼TestContext框架就不能在測試類之間緩存應用程序上下文,因此構建過程的運行速度會明顯變慢。

上下文緩存的大小以默認的最大大小32爲界限。每當達到最大大小時,就使用最少最近使用(LRU)的清除策略來驅逐和關閉陳舊的上下文。您可以通過設置一個名爲spring.test.context.cache.maxSize的JVM系統屬性,從命令行或構建腳本中配置最大大小。作爲替代方案,您可以通過使用SpringProperties API以編程方式設置相同的屬性。

由於在給定的測試套件中加載了大量的應用程序上下文可能會導致該套件花費不必要的時間來執行,因此準確地知道已經加載和緩存了多少上下文通常是有益的。要查看底層上下文緩存的統計信息,您可以設置org.springframework.test.context的日誌級別。要調試的緩存日誌記錄類別。

在可能的情況下,一個測試應用程序上下文導致腐敗,需要重新加載(例如,通過修改一個bean定義或應用程序對象的狀態),你可以用@DirtiesContext註釋您的測試類或測試方法(見@DirtiesContext @DirtiesContext)的討論。這指示Spring從緩存中刪除上下文並在運行下一個需要相同應用程序上下文的測試之前重新構建應用程序上下文。注意,對@DirtiesContext註釋的支持是由DirtiesContextBeforeModesTestExecutionListener和DirtiesContextTestExecutionListener提供的,它們在默認情況下是啓用的。

上下文層次結構
在編寫依賴於加載的Spring ApplicationContext的集成測試時,通常只需要針對單個上下文進行測試就足夠了。但是,有時根據ApplicationContext實例的層次結構進行測試是有益的,甚至是必要的。例如,如果您正在開發一個Spring MVC web應用程序,您通常有一個由Spring的ContextLoaderListener加載的根WebApplicationContext和一個由Spring的DispatcherServlet加載的子WebApplicationContext。這導致了父子上下文層次結構,其中共享組件和基礎設施配置在根上下文中聲明,並由特定於web的組件在子上下文中使用。另一個用例可以在Spring批處理應用程序中找到,其中通常有一個爲共享批處理基礎結構提供配置的父上下文和一個爲特定批處理作業配置的子上下文。

您可以編寫使用上下文層次結構的集成測試,方法是在單個測試類或測試類層次結構中使用@ context thier阿其註釋聲明上下文配置。如果在一個測試類層次結構中的多個類上聲明瞭一個上下文層次結構,那麼您還可以合併或覆蓋上下文層次結構中指定的指定級別的上下文配置。在爲層次結構中的給定級別合併配置時,配置資源類型(即XML配置文件或組件類)必須是一致的。否則,在上下文層次結構中使用不同的資源類型配置不同的級別是完全可以接受的。

本節中其餘的基於JUnit Jupiter的示例展示了集成測試的常見配置場景,這些場景需要使用上下文層次結構。

帶有上下文層次結構的單個測試類
ControllerIntegrationTests代表一個典型的集成測試場景的Spring MVC web應用程序通過聲明一個上下文層次結構,由兩個水平,一根WebApplicationContext(由使用TestAppConfig @ configuration類加載),一個用於dispatcher servlet WebApplicationContext(由使用WebConfig @ configuration類加載)。被自動導入測試實例的WebApplicationContext是用於子上下文的上下文(即,層次結構中最低的上下文)。下面的清單顯示了這個配置場景:

@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextHierarchy({
    @ContextConfiguration(classes = TestAppConfig.class),
    @ContextConfiguration(classes = WebConfig.class)
})
class ControllerIntegrationTests {

    @Autowired
    WebApplicationContext wac;

    // ...
}

具有隱式父上下文的類層次結構
本例中的測試類定義了測試類層次結構中的上下文層次結構。AbstractWebTests在spring支持的web應用程序中聲明根WebApplicationContext的配置。但是請注意,AbstractWebTests並沒有聲明@上下文層次結構。因此,AbstractWebTests的子類可以選擇參與上下文層次結構,或者遵循@ContextConfiguration的標準語義。SoapWebServiceTests和RestWebServiceTests都對AbstractWebTests進行了擴展,並使用@ context層次結構定義了上下文層次結構。結果是加載了三個應用程序上下文(每個@ContextConfiguration聲明一個上下文),基於AbstractWebTests中的配置加載的應用程序上下文被設置爲爲具體子類加載的每個上下文的父上下文。下面的清單顯示了這個配置場景:

@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
public abstract class AbstractWebTests {}

@ContextHierarchy(@ContextConfiguration("/spring/soap-ws-config.xml"))
public class SoapWebServiceTests extends AbstractWebTests {}

@ContextHierarchy(@ContextConfiguration("/spring/rest-ws-config.xml"))
public class RestWebServiceTests extends AbstractWebTests {}

具有合併上下文層次結構配置的類層次結構
本例中的類顯示了使用命名層次結構層次來合併上下文層次結構中特定層次的配置。BaseTests在層次結構中定義了兩個級別,父級和子級。ExtendedTests擴展了基測試,並指示Spring TestContext框架合併子層次結構級別的上下文配置,方法是確保在@ContextConfiguration的name屬性中聲明的名稱都是子元素。結果是加載了三個應用程序上下文:一個for /app-config。xml,一個for /user-config。和一個用於{"/user-config。xml”、“/ order-config.xml”}。與前面的示例一樣,從/app-config.xml加載的應用程序上下文被設置爲從/user-config.xml和{"/user-config. xml "加載的上下文的父上下文。xml”、“/ order-config.xml”}。下面的清單顯示了這個配置場景:

@ExtendWith(SpringExtension.class)
@ContextHierarchy({
    @ContextConfiguration(name = "parent", locations = "/app-config.xml"),
    @ContextConfiguration(name = "child", locations = "/user-config.xml")
})
class BaseTests {}

@ContextHierarchy(
    @ContextConfiguration(name = "child", locations = "/order-config.xml")
)
class ExtendedTests extends BaseTests {}

類層次結構,具有重寫的上下文層次結構配置
與前面的示例不同,這個示例演示瞭如何通過將@ContextConfiguration中的inheritLocations標記設置爲false來覆蓋上下文層次結構中給定的指定級別的配置。因此,ExtendedTests的應用程序上下文僅從/test-user config.xml加載,並將其父設置爲從/app-config.xml加載的上下文。下面的清單顯示了這個配置場景:

@ExtendWith(SpringExtension.class)
@ContextHierarchy({
    @ContextConfiguration(name = "parent", locations = "/app-config.xml"),
    @ContextConfiguration(name = "child", locations = "/user-config.xml")
})
class BaseTests {}

@ContextHierarchy(
    @ContextConfiguration(
        name = "child",
        locations = "/test-user-config.xml",
        inheritLocations = false
))
class ExtendedTests extends BaseTests {}

注意:

在上下文層次結構中清除上下文
如果您在測試中使用@DirtiesContext,而測試的上下文被配置爲上下文層次結構的一部分,那麼您可以使用hierarchyMode標誌來控制上下文緩存的清除方式。有關更多細節,請參見Spring測試註釋中的@DirtiesContext和@DirtiesContext javadoc中的討論。

3.5.6 測試夾具的依賴注入
當您使用DependencyInjectionTestExecutionListener(它是默認配置的)時,您的測試實例的依賴關係是從您用@ContextConfiguration或相關注釋配置的應用程序上下文中的bean注入的。您可以使用setter注入,字段注入,或者兩者都使用,這取決於您選擇的註釋以及您是否將它們放在setter方法或字段上。如果您正在使用JUnit Jupiter,您也可以選擇使用構造函數注入(參見SpringExtension的依賴注入)。爲了與Spring基於註解的注入支持保持一致,您還可以使用Spring的@Autowired註解或者JSR-330中的@Inject註解來進行字段和setter注入。

注意:對於JUnit Jupiter以外的測試框架,TestContext框架不參與測試類的實例化。因此,對構造函數使用@Autowired或@Inject對測試類沒有影響。

雖然在生產代碼中不鼓勵字段注入,但在測試代碼中字段注入實際上是很自然的。區別的基本原理是您永遠不會直接實例化您的測試類。因此,不需要能夠調用測試類上的公共構造函數或setter方法。

因爲@Autowired被用來按類型執行自動裝配,如果你有多個相同類型的bean定義,你就不能依靠這個方法來處理那些特定的bean。在這種情況下,您可以將@Autowired與@Qualifier結合使用。您還可以選擇將@Inject與@Named結合使用。或者,如果您的測試類可以訪問它的ApplicationContext,那麼您可以使用(例如)對ApplicationContext的調用來執行顯式查找。getBean (“titleRepository TitleRepository.class)。

如果您不希望依賴項注入應用於您的測試實例,那麼不要使用@Autowired或@Inject來註釋字段或setter方法。或者,您可以通過顯式地使用@ testexecutionlistener配置您的類,並從偵聽器列表中刪除DependencyInjectionTestExecutionListener.class,從而完全禁用依賴項注入。

考慮一下測試HibernateTitleRepository類的場景,如目標部分所述。接下來的兩個代碼清單演示瞭如何在字段和setter方法上使用@Autowired。應用程序上下文配置在所有示例代碼清單之後顯示。

注意:

以下代碼清單中的依賴項注入行爲並不特定於JUnit Jupiter。同樣的DI技術可以與任何受支持的測試框架一起使用。

下面的示例調用靜態斷言方法,如assertNotNull(),但不使用斷言作爲調用的前綴。在這種情況下,假設該方法是通過import靜態聲明正確導入的,而該靜態聲明在示例中沒有顯示。

第一個代碼清單顯示了一個基於JUnit Jupiter的測試類實現,它使用@Autowired進行字段注入:

@ExtendWith(SpringExtension.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

    // this instance will be dependency injected by type
    @Autowired
    HibernateTitleRepository titleRepository;

    @Test
    void findById() {
        Title title = titleRepository.findById(new Long(10));
        assertNotNull(title);
    }
}

或者,您可以配置類爲使用@Autowired進行setter注入,如下所示:

@ExtendWith(SpringExtension.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {

    // this instance will be dependency injected by type
    HibernateTitleRepository titleRepository;

    @Autowired
    void setTitleRepository(HibernateTitleRepository titleRepository) {
        this.titleRepository = titleRepository;
    }

    @Test
    void findById() {
        Title title = titleRepository.findById(new Long(10));
        assertNotNull(title);
    }
}

前面的代碼清單使用與@ContextConfiguration註釋(即repository-config.xml)引用的相同的XML上下文文件。下面顯示了這個配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- this bean will be injected into the HibernateTitleRepositoryTests class -->
    <bean id="titleRepository" class="com.foo.repository.hibernate.HibernateTitleRepository">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <!-- configuration elided for brevity -->
    </bean>

</beans>

注意:

如果您從spring提供的測試基類擴展,而這個測試基類碰巧在其setter方法上使用@Autowired,那麼您可能在應用程序上下文中定義了多個受影響類型的bean(例如,多個數據源bean)。在這種情況下,您可以覆蓋setter方法,並使用@Qualifier註釋來指示特定的目標bean,如下所示(但也要確保委派給超類中被覆蓋的方法):

// ...

    @Autowired
    @Override
    public void setDataSource(@Qualifier("myDataSource") DataSource dataSource) {
        super.setDataSource(dataSource);
    }

// ...

指定的限定符值指示要注入的特定數據源bean,從而將類型匹配集縮小到特定bean。它的值與對應的<bean>定義中的<限定符>聲明相匹配。bean名稱用作回退限定符值,因此您還可以通過名稱有效地指向一個特定的bean(如前面所示,假設myDataSource是bean id)。

3.5.7 測試請求和會話範圍的bean
從早期開始,Spring就支持請求作用域bean和會話作用域bean,您可以通過以下步驟測試請求作用域bean和會話作用域bean:

  • 通過使用@WebAppConfiguration註釋測試類,確保爲您的測試加載了WebApplicationContext。
  • 將模擬請求或會話注入到測試實例中,並根據需要準備測試裝置。
  • 調用從配置的WebApplicationContext中檢索的web組件(使用依賴項注入)。
  • 針對模擬執行斷言。

下一個代碼片段顯示了登錄用例的XML配置。請注意,userService bean依賴於請求範圍的loginAction bean。另外,LoginAction通過使用SpEL表達式實例化,SpEL表達式從當前HTTP請求檢索用戶名和密碼。在我們的測試中,我們希望通過TestContext框架管理的模擬來配置這些請求參數。下面的清單顯示了這個用例的配置:

請求範圍內bean配置

<beans>

    <bean id="userService" class="com.example.SimpleUserService"
            c:loginAction-ref="loginAction"/>

    <bean id="loginAction" class="com.example.LoginAction"
            c:username="#{request.getParameter('user')}"
            c:password="#{request.getParameter('pswd')}"
            scope="request">
        <aop:scoped-proxy/>
    </bean>

</beans>

在RequestScopedBeanTests中,我們將UserService(即被測試的主題)和MockHttpServletRequest注入到我們的測試實例中。在我們的requestScope()測試方法中,我們通過在提供的MockHttpServletRequest中設置請求參數來設置測試裝置。當在我們的userService上調用loginUser()方法時,我們確信用戶服務可以訪問當前MockHttpServletRequest的請求範圍的loginAction(也就是說,我們只是在其中設置參數)。然後,我們可以根據已知的用戶名和密碼輸入對結果執行斷言。下面的清單顯示瞭如何做到這一點:

@SpringJUnitWebConfig
class RequestScopedBeanTests {

    @Autowired UserService userService;
    @Autowired MockHttpServletRequest request;

    @Test
    void requestScope() {
        request.setParameter("user", "enigma");
        request.setParameter("pswd", "$pr!ng");

        LoginResults results = userService.loginUser();
        // assert results
    }
}

下面的代碼片段與我們前面看到的用於請求作用域的bean的代碼片段類似。但是,這一次,userService bean依賴於會話範圍的userPreferences bean。請注意,UserPreferences bean是通過使用從當前HTTP會話檢索主題的SpEL表達式來實例化的。在我們的測試中,我們需要在TestContext框架管理的模擬會話中配置一個主題。下面的例子演示瞭如何做到這一點:

會話範圍內的bean配置

<beans>

    <bean id="userService" class="com.example.SimpleUserService"
            c:userPreferences-ref="userPreferences" />

    <bean id="userPreferences" class="com.example.UserPreferences"
            c:theme="#{session.getAttribute('theme')}"
            scope="session">
        <aop:scoped-proxy/>
    </bean>

</beans>

在SessionScopedBeanTests中,我們將UserService和MockHttpSession注入到我們的測試實例中。在我們的sessionScope()測試方法中,我們通過在提供的MockHttpSession中設置預期的主題屬性來設置測試裝置。在userService上調用processUserPreferences()方法時,我們確信用戶服務可以訪問當前MockHttpSession的會話範圍的userPreferences,並且我們可以根據配置的主題對結果執行斷言。下面的例子演示瞭如何做到這一點:

@SpringJUnitWebConfig
class SessionScopedBeanTests {

    @Autowired UserService userService;
    @Autowired MockHttpSession session;

    @Test
    void sessionScope() throws Exception {
        session.setAttribute("theme", "blue");

        Results results = userService.processUserPreferences();
        // assert results
    }
}

3.5.8. Transaction Management

在TestContext框架中,事務由TransactionalTestExecutionListener管理,這是默認配置的,即使您沒有在測試類上顯式地聲明@ testexecutionlistener。然而,要啓用對事務的支持,您必須在ApplicationContext中配置一個PlatformTransactionManager bean,該bean使用@ContextConfiguration語義加載(稍後將提供進一步的詳細信息)。此外,您必須在測試的類或方法級別聲明Spring的@Transactional註釋。

Test-managed事務
測試管理的事務是通過使用TransactionalTestExecutionListener以聲明方式管理的事務,或者通過使用TestTransaction(稍後將進行描述)以編程方式管理的事務。您不應該將這些事務與Spring管理的事務(那些由Spring在爲測試加載的ApplicationContext中直接管理的事務)或應用程序管理的事務(那些由測試調用的應用程序代碼中以編程方式管理的事務)相混淆。spring管理的事務和應用程序管理的事務通常參與測試管理的事務。但是,如果spring管理的或應用程序管理的事務配置了任何傳播類型,而不是必需的或支持的,則應該謹慎使用(有關詳細信息,請參閱關於transaction propagation的討論)。

警告:搶佔式超時和測試管理的事務
在與Spring的測試管理事務一起使用測試框架的任何形式的搶佔式超時時,必須謹慎。

具體來說,Spring的測試支持將事務狀態綁定到當前線程(通過java.lang實現)。在調用當前測試方法之前。如果測試框架在新線程中調用當前測試方法以支持搶佔式超時,則在當前測試方法中執行的任何操作都不會在測試管理的事務中調用。因此,任何此類操作的結果都不會在測試管理的事務中回滾。相反,即使Spring正確地回滾了測試管理的事務,這些操作也將提交到持久存儲(例如關係數據庫)。

這種情況包括但不限於以下情況。

  • JUnit 4的@Test(timeout =…)支持和超時規則
  • JUnit Jupiter的asserttimeoutpreemp(…)方法。斷言類
  • TestNG的@Test(timeOut =…)支持

啓用和禁用事務
使用@Transactional註釋測試方法將導致在事務中運行測試,默認情況下,事務在測試完成後自動回滾。如果一個測試類被@Transactional註釋,那麼類層次結構中的每個測試方法都在一個事務中運行。沒有使用@Transactional註釋的測試方法(在類或方法級別)不會在事務中運行。注意,測試生命週期方法不支持@Transactional——例如,用JUnit Jupiter的@BeforeAll、@BeforeEach等註釋的方法。而且,使用@Transactional註釋但將傳播屬性設置爲NOT_SUPPORTED的測試不會在事務中運行。

Attribute Supported for test-managed transactions

value and transactionManager

yes

propagation

only Propagation.NOT_SUPPORTED is supported

isolation

no

timeout

no

readOnly

no

rollbackFor and rollbackForClassName

no: use TestTransaction.flagForRollback() instead

noRollbackFor and noRollbackForClassName

no: use TestTransaction.flagForCommit() instead

注意:方法級生命週期方法——例如,用JUnit Jupiter的@BeforeEach或@AfterEach註釋的方法——在一個測試管理的事務中運行。另一方面,suite-level和class-level生命週期方法—例如,用JUnit Jupiter的@BeforeAll或@AfterAll註釋的方法和用TestNG的@BeforeSuite、@AfterSuite、@BeforeClass或@AfterClass註釋的方法—不在測試管理的事務中運行。

如果您需要在事務中執行suite級或類級生命週期方法中的代碼,您可能希望將相應的PlatformTransactionManager注入到您的測試類中,然後將其與TransactionTemplate一起用於程序化事務管理。

注意,AbstractTransactionalJUnit4SpringContextTests和AbstractTransactionalTestNGSpringContextTests是預先配置的,用於類級別的事務支持。
下面的例子演示了一個爲基於hibernate的用戶庫編寫集成測試的常見場景:

@SpringJUnitConfig(TestConfig.class)
@Transactional
class HibernateUserRepositoryTests {

    @Autowired
    HibernateUserRepository repository;

    @Autowired
    SessionFactory sessionFactory;

    JdbcTemplate jdbcTemplate;

    @Autowired
    void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Test
    void createUser() {
        // track initial state in test database:
        final int count = countRowsInTable("user");

        User user = new User(...);
        repository.save(user);

        // Manual flush is required to avoid false positive in test
        sessionFactory.getCurrentSession().flush();
        assertNumUsers(count + 1);
    }

    private int countRowsInTable(String tableName) {
        return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
    }

    private void assertNumUsers(int expected) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
    }
}

正如在事務回滾和提交行爲中所解釋的,在createUser()方法運行後不需要清理數據庫,因爲對數據庫所做的任何更改都會被TransactionalTestExecutionListener自動回滾。

事務回滾和提交行爲
默認情況下,測試事務將在測試完成後自動回滾;然而,事務性提交和回滾行爲可以通過@Commit和@Rollback註釋進行聲明性配置。有關詳細信息,請參閱註釋支持部分中的相應條目。

編程式事務管理
您可以通過使用TestTransaction中的靜態方法以編程方式與測試管理的事務進行交互。例如,您可以在測試方法內部、方法之前和方法之後使用TestTransaction來啓動或結束當前的測試管理事務,或者配置當前的測試管理事務以進行回滾或提交。只要啓用了TransactionalTestExecutionListener,就會自動提供對TestTransaction的支持。

下面的示例演示了TestTransaction的一些特性。有關更多細節,請參見TestTransaction的javadoc。

@ContextConfiguration(classes = TestConfig.class)
public class ProgrammaticTransactionManagementTests extends
        AbstractTransactionalJUnit4SpringContextTests {

    @Test
    public void transactionalTest() {
        // assert initial state in test database:
        assertNumUsers(2);

        deleteFromTables("user");

        // changes to the database will be committed!
        TestTransaction.flagForCommit();
        TestTransaction.end();
        assertFalse(TestTransaction.isActive());
        assertNumUsers(0);

        TestTransaction.start();
        // perform other actions against the database that will
        // be automatically rolled back after the test completes...
    }

    protected void assertNumUsers(int expected) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
    }
}

在事務外部運行代碼
有時候,您可能需要事務之前或之後執行某些代碼測試方法但在事務上下文——例如,驗證初始數據庫狀態之前運行您的測試或驗證預期的事務提交行爲測試運行後(如果測試配置提交事務)。TransactionalTestExecutionListener支持@BeforeTransaction和@AfterTransaction註釋。您可以使用這些註釋之一來註釋測試類中的任何void方法或測試接口中的任何void默認方法,而TransactionalTestExecutionListener將確保您的事務前方法或事務後方法在適當的時間運行。

注意:任何before方法(例如用JUnit Jupiter的@BeforeEach註釋的方法)和任何after方法(例如用JUnit Jupiter的@AfterEach註釋的方法)都在一個事務中運行。此外,對於沒有配置爲在事務中運行的測試方法,不會運行@BeforeTransaction或@AfterTransaction註釋的方法。

配置事務管理器
TransactionalTestExecutionListener期望在Spring ApplicationContext中爲測試定義一個PlatformTransactionManager bean。如果在測試的ApplicationContext中有多個PlatformTransactionManager實例,您可以使用@Transactional(“myTxMgr”)或@Transactional(transactionManager =“myTxMgr”)聲明一個限定符,或者使用@Configuration類實現TransactionManagementConfigurer。有關用於在測試的ApplicationContext中查找事務管理器的算法的詳細信息,請參考TestContextTransactionUtils.retrieveTransactionManager()的javadoc。

演示所有與事務相關的註釋
下面基於JUnit Jupiter的示例顯示了一個虛構的集成測試場景,其中突出顯示了所有與事務相關的註釋。這個示例並不打算演示最佳實踐,而是演示如何使用這些註釋。有關更多信息和配置示例,請參閱註釋支持部分。@Sql的事務管理包含一個附加示例,該示例使用@Sql執行具有默認事務回滾語義的聲明性SQL腳本。下面的例子展示了相關的註釋:

@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {

    @BeforeTransaction
    void verifyInitialDatabaseState() {
        // logic to verify the initial state before a transaction is started
    }

    @BeforeEach
    void setUpTestDataWithinTransaction() {
        // set up test data within the transaction
    }

    @Test
    // overrides the class-level @Commit setting
    @Rollback
    void modifyDatabaseWithinTransaction() {
        // logic which uses the test data and modifies database state
    }

    @AfterEach
    void tearDownWithinTransaction() {
        // execute "tear down" logic within the transaction
    }

    @AfterTransaction
    void verifyFinalDatabaseState() {
        // logic to verify the final state after transaction has rolled back
    }

}

注意:

在測試ORM代碼時避免誤報
當您測試操作Hibernate會話或JPA持久性上下文狀態的應用程序代碼時,請確保在運行該代碼的測試方法中刷新底層工作單元。未能清除底層工作單元可能會產生誤報:測試通過,但是相同的代碼在活動的生產環境中拋出異常。請注意,這適用於維護內存中工作單元的任何ORM框架。在下面的基於hibernate的示例測試用例中,一種方法顯示假陽性,另一種方法正確顯示刷新會話的結果:

// ...

@Autowired
SessionFactory sessionFactory;

@Transactional
@Test // no expected exception!
public void falsePositive() {
    updateEntityInHibernateSession();
    // False positive: an exception will be thrown once the Hibernate
    // Session is finally flushed (i.e., in production code)
}

@Transactional
@Test(expected = ...)
public void updateWithSessionFlush() {
    updateEntityInHibernateSession();
    // Manual flush is required to avoid false positive in test
    sessionFactory.getCurrentSession().flush();
}

// ...

下面的例子顯示了JPA的匹配方法:

// ...

@PersistenceContext
EntityManager entityManager;

@Transactional
@Test // no expected exception!
public void falsePositive() {
    updateEntityInJpaPersistenceContext();
    // False positive: an exception will be thrown once the JPA
    // EntityManager is finally flushed (i.e., in production code)
}

@Transactional
@Test(expected = ...)
public void updateWithEntityManagerFlush() {
    updateEntityInJpaPersistenceContext();
    // Manual flush is required to avoid false positive in test
    entityManager.flush();
}

// ...

3.5.9. Executing SQL Scripts

在對關係數據庫編寫集成測試時,執行SQL腳本來修改數據庫模式或將測試數據插入表中通常是有益的。Spring -jdbc模塊支持通過在加載Spring ApplicationContext時執行SQL腳本來初始化嵌入式或現有數據庫。有關詳細信息,請參閱嵌入式數據庫支持和使用嵌入式數據庫測試數據訪問邏輯。

雖然在加載ApplicationContext時初始化數據庫以進行測試非常有用,但有時在集成測試期間修改數據庫是必要的。下面幾節將解釋如何在集成測試期間以編程方式和聲明方式執行SQL腳本。

以編程方式執行SQL腳本
Spring爲在集成測試方法中以編程方式執行SQL腳本提供了以下選項。

  • org.springframework.jdbc.datasource.init.ScriptUtils
  • org.springframework.jdbc.datasource.init.ResourceDatabasePopulator
  • org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests
  • org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests

ScriptUtils提供了一組用於處理SQL腳本的靜態實用程序方法,主要用於框架內部。但是,如果您需要完全控制SQL腳本的解析和執行方式,那麼ScriptUtils可能比後面介紹的其他替代方法更適合您的需要。有關更多詳細信息,請參閱ScriptUtils中的各個方法的javadoc。

ResourceDatabasePopulator提供了一個基於對象的API,用於使用外部資源中定義的SQL腳本以編程方式填充、初始化或清理數據庫。ResourceDatabasePopulator提供用於配置字符編碼、語句分隔符、註釋分隔符和解析和運行腳本時使用的錯誤處理標誌的選項。每個配置選項都有一個合理的默認值。有關默認值的詳細信息,請參閱javadoc。要運行ResourceDatabasePopulator中配置的腳本,您可以調用populate(連接)方法來對java.sql執行populator。連接或執行(DataSource)方法來針對javax.sql.DataSource執行填充程序。下面的示例爲測試模式和測試數據指定SQL腳本,將語句分隔符設置爲@@,並針對數據源執行腳本:

@Test
void databaseTest() {
    ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
    populator.addScripts(
            new ClassPathResource("test-schema.sql"),
            new ClassPathResource("test-data.sql"));
    populator.setSeparator("@@");
    populator.execute(this.dataSource);
    // execute code that uses the test schema and data
}

注意ResourceDatabasePopulator在內部委託給ScriptUtils來解析和運行SQL腳本。類似地,AbstractTransactionalJUnit4SpringContextTests和AbstractTransactionalTestNGSpringContextTests中的executeSqlScript(..)方法在內部使用ResourceDatabasePopulator運行SQL腳本。有關更多詳細信息,請參閱各種executeSqlScript(..)方法的javadoc。

使用@Sql聲明性地執行SQL腳本
除了前面提到的以編程方式運行SQL腳本的機制之外,您還可以聲明性地在Spring TestContext框架中配置SQL腳本。具體來說,您可以在測試類或測試方法上聲明@Sql註釋,以配置各個SQL語句或SQL腳本的資源路徑,這些腳本應該在集成測試方法之前或之後在給定的數據庫上運行。SqlScriptsTestExecutionListener提供了對@Sql的支持,它在默認情況下是啓用的。

注意:方法級@Sql聲明默認情況下覆蓋類級聲明。但是,從Spring Framework 5.2開始,可以通過@SqlMergeMode爲每個測試類或每個測試方法配置此行爲。有關詳細信息,請參見使用@SqlMergeMode合併和覆蓋配置。

path資源的語義
每個路徑都被解釋爲一個Spring資源。普通路徑(例如,“schema.sql”)被視爲與定義測試類的包相關的類路徑資源。以斜槓開頭的路徑被視爲絕對類路徑資源(例如,“/org/example/schema.sql”)。引用URL的路徑(例如,以classpath:、file:、http:爲前綴的路徑)是通過使用指定的資源協議加載的。

下面的示例展示瞭如何在基於JUnit Jupiter的集成測試類的類級和方法級使用@Sql:

@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {

    @Test
    void emptySchemaTest() {
        // execute code that uses the test schema without any test data
    }

    @Test
    @Sql({"/test-schema.sql", "/test-user-data.sql"})
    void userTest() {
        // execute code that uses the test schema and test data
    }
}

默認的腳本檢測
如果沒有指定SQL腳本或語句,則根據@Sql聲明的位置嘗試檢測默認腳本。如果無法檢測到默認值,則拋出IllegalStateException。

  • 類級別聲明:如果帶註釋的測試類是com.example。對應的默認腳本是classpath:com/example/MyTest.sql。
  • 方法級聲明:如果帶註釋的測試方法名爲testMethod(),並在com.example類中定義。對應的默認腳本是classpath:com/example/MyTest.testMethod.sql。

聲明多個@Sql集合
如果您需要爲一個給定的測試類或測試方法配置多個SQL腳本集,但是使用不同的語法配置、不同的錯誤處理規則或每個集的不同執行階段,那麼您可以聲明@Sql的多個實例。使用Java 8,可以使用@Sql作爲可重複的註釋。否則,可以使用@SqlGroup註釋作爲顯式容器來聲明@Sql的多個實例。

下面的示例展示瞭如何使用@Sql作爲Java 8的可重複註釋:

@Test
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`"))
@Sql("/test-user-data.sql")
void userTest() {
    // execute code that uses the test schema and test data
}

在前面的示例場景中,測試模式。sql腳本對單行註釋使用不同的語法。
下面的示例與前面的示例相同,不同之處在於@Sql聲明被分組在@SqlGroup中。對於Java 8及以上版本,使用@SqlGroup是可選的,但是爲了與其他JVM語言(如Kotlin)兼容,您可能需要使用@SqlGroup。

@Test
@SqlGroup({
    @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
    @Sql("/test-user-data.sql")
)}
void userTest() {
    // execute code that uses the test schema and test data
}

腳本執行階段
默認情況下,SQL腳本在相應的測試方法之前執行。但是,如果您需要在測試方法之後運行一組特定的腳本(例如,清理數據庫狀態),您可以使用@Sql中的executionPhase屬性,如下面的示例所示:

@Test
@Sql(
    scripts = "create-test-data.sql",
    config = @SqlConfig(transactionMode = ISOLATED)
)
@Sql(
    scripts = "delete-test-data.sql",
    config = @SqlConfig(transactionMode = ISOLATED),
    executionPhase = AFTER_TEST_METHOD
)
void userTest() {
    // execute code that needs the test data to be committed
    // to the database outside of the test's transaction
}

注意,隔離和AFTER_TEST_METHOD是從Sql靜態導入的。TransactionMode和Sql。分別ExecutionPhase。

使用@SqlConfig進行腳本配置
可以使用@SqlConfig註釋配置腳本解析和錯誤處理。當聲明爲集成測試類上的類級註釋時,@SqlConfig充當測試類層次結構中所有SQL腳本的全局配置。當使用@Sql註釋的config屬性直接聲明時,@SqlConfig充當在封閉的@Sql註釋中聲明的SQL腳本的本地配置。@SqlConfig中的每個屬性都有一個隱式的默認值,該值記錄在相應屬性的javadoc中。由於Java語言規範中爲註釋屬性定義的規則,不幸的是,不可能將null值賦給註釋屬性。因此,爲了支持對繼承的全局配置的覆蓋,@SqlConfig屬性的顯式默認值爲“”(用於字符串)、{}(用於數組)或default(用於枚舉)。這種方法允許@SqlConfig的局部聲明選擇性地覆蓋@SqlConfig的全局聲明中的單個屬性,方法是提供一個不同於“”、{}或默認值的值。當本地@SqlConfig屬性不提供“”、{}或默認值以外的顯式值時,將繼承全局@SqlConfig屬性。因此,顯式本地配置覆蓋全局配置。

@Sql和@SqlConfig提供的配置選項與ScriptUtils和ResourceDatabasePopulator支持的配置選項相同,但是是<jdbc:initialize-database/> XML namespace元素提供的配置選項的超集。有關詳細信息,請參見@Sql和@SqlConfig中各個屬性的javadoc。

@Sql的事務管理
默認情況下,SqlScriptsTestExecutionListener爲使用@Sql配置的腳本推斷所需的事務語義。具體來說,SQL腳本運行沒有交易,在現有spring管理事務(例如,事務管理的TransactionalTestExecutionListener測試與@ transactional註釋),或在一個孤立的事務,這取決於transactionMode屬性的配置價值@SqlConfig和PlatformTransactionManager測試的ApplicationContext的存在。但是,最基本的要求是javax.sql。數據源必須出現在測試的ApplicationContext中。

如果SqlScriptsTestExecutionListener用於檢測數據源和平臺transactionManager並推斷事務語義的算法不適合您的需要,您可以通過設置@SqlConfig的數據源和transactionManager屬性來指定顯式名稱。此外,您可以通過設置@SqlConfig的transactionMode屬性來控制事務傳播行爲(例如,腳本是否應該在獨立的事務中運行)。雖然對所有支持的全面討論選擇事務管理與@Sql超出了這個範圍參考手冊,@SqlConfig javadoc和SqlScriptsTestExecutionListener提供詳細信息,和下面的示例顯示了一個典型的測試場景中,使用JUnit木星和事務與@Sql測試:

@SpringJUnitConfig(TestDatabaseConfig.class)
@Transactional
class TransactionalSqlScriptsTests {

    final JdbcTemplate jdbcTemplate;

    @Autowired
    TransactionalSqlScriptsTests(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Test
    @Sql("/test-data.sql")
    void usersTest() {
        // verify state in test database:
        assertNumUsers(2);
        // execute code that uses the test data...
    }

    int countRowsInTable(String tableName) {
        return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
    }

    void assertNumUsers(int expected) {
        assertEquals(expected, countRowsInTable("user"),
            "Number of rows in the [user] table.");
    }
}

注意,在運行usersTest()方法之後,不需要清理數據庫,因爲對數據庫所做的任何更改(在測試方法內或在/test-data內)。由TransactionalTestExecutionListener自動回滾(有關詳細信息,請參閱事務管理)。

使用@SqlMergeMode合併和覆蓋配置
從Spring Framework 5.2開始,可以將方法級@Sql聲明與類級聲明合併。例如,這允許您爲每個測試類提供一次數據庫模式或一些公共測試數據的配置,然後爲每個測試方法提供額外的、特定於用例的測試數據。要啓用@Sql合併,請使用@SqlMergeMode(MERGE)註釋您的測試類或測試方法。要禁用特定測試方法(或特定測試子類)的合併,可以通過@SqlMergeMode(OVERRIDE)切換回默認模式。有關示例和詳細信息,請參閱@SqlMergeMode註釋文檔一節。

3.5.10 並行測試執行

當使用Spring TestContext框架時,Spring Framework 5.0引入了對在單個JVM中並行執行測試的基本支持。通常,這意味着大多數測試類或測試方法可以並行執行,而不需要對測試代碼或配置進行任何更改。

注意:有關如何設置並行測試執行的詳細信息,請參閱測試框架、構建工具或IDE的文檔。

請記住,將併發性引入您的測試套件可能會導致意想不到的副作用、奇怪的運行時行爲,以及間歇性地或看似隨機地失敗的測試。因此,Spring團隊爲何時不併行執行測試提供了以下一般指導原則。

如果下列測試:

  • 使用Spring框架的@DirtiesContext支持。
  • 使用Spring Boot的@MockBean或@SpyBean支持。
  • 使用JUnit 4的@FixMethodOrder支持或任何設計來確保測試方法以特定順序運行的測試框架特性。但是,請注意,如果整個測試類是並行執行的,那麼這並不適用。
  • 更改共享服務或系統(如數據庫、消息代理、文件系統等)的狀態。這既適用於嵌入式系統,也適用於外部系統。

注意:如果並行測試執行失敗,並且出現異常,說明當前測試的ApplicationContext不再活動,這通常意味着ApplicationContext已從另一個線程的ContextCache中刪除。

這可能是由於使用了@DirtiesContext或自動從ContextCache中清除。如果@DirtiesContext是罪魁禍首,那麼您需要找到一種方法來避免使用@DirtiesContext或者從並行執行中排除這些測試。如果已經超過了ContextCache的最大大小,則可以增加緩存的最大大小。有關詳細信息,請參閱關於上下文緩存的討論。

警告:只有在底層TestContext實現提供了一個複製構造函數的情況下,才能在Spring TestContext框架中執行並行測試,正如TestContext的javadoc中所解釋的那樣。Spring中使用的DefaultTestContext提供了這樣一個構造函數。但是,如果您使用提供自定義TestContext實現的第三方庫,您需要驗證它是否適合並行測試執行。

3.5.11 TestContext框架支持類
本節描述支持Spring TestContext框架的各種類。

Spring JUnit 4 Runner
Spring TestContext框架通過自定義運行器(支持JUnit 4.12或更高版本)提供了與JUnit 4的完整集成。通過註釋測試類@RunWith (SpringJUnit4ClassRunner.class)或短@RunWith (SpringRunner.class)變體,開發人員可以實現標準的JUnit 4-based單元和集成測試,同時收穫的好處和TestContext框架,比如支持加載應用程序上下文,依賴注入的測試實例,事務測試方法執行,等等。如果您希望將Spring TestContext框架與另一個運行器(如JUnit 4的參數化運行器)或第三方運行器(如MockitoJUnitRunner)一起使用,您可以選擇使用Spring對JUnit規則的支持。
下面的代碼清單顯示了配置一個測試類來運行自定義Spring Runner的最低要求:

@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {

    @Test
    public void testMethod() {
        // execute test logic...
    }
}

在前面的示例中,@ testexecutionlistener配置了一個空列表,以禁用默認監聽器,否則將需要通過@ContextConfiguration配置ApplicationContext。

Spring JUnit 4規則
rules包提供了以下JUnit 4規則(在JUnit 4.12或更高版本上受支持):

  • SpringClassRule
  • SpringMethodRule

SpringClassRule是一個JUnit TestRule,它支持Spring TestContext框架的類級特性,而SpringMethodRule是一個JUnit MethodRule,它支持Spring TestContext框架的實例級和方法級特性。

與SpringRunner不同,Spring基於規則的JUnit支持的優點是獨立於任何org.junit.runner.Runner實現,因此可以與現有的備選運行程序(如JUnit 4的參數化運行程序)或第三方運行程序(如MockitoJUnitRunner)結合使用。

爲了支持TestContext框架的全部功能,您必須將SpringClassRule與SpringMethodRule結合起來。下面的例子展示了在集成測試中聲明這些規則的正確方法:

// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
public class IntegrationTest {

    @ClassRule
    public static final SpringClassRule springClassRule = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    @Test
    public void testMethod() {
        // execute test logic...
    }
}

JUnit 4 Support Classes
org.springframework.test.context。junit4包爲基於JUnit 4的測試用例提供了以下支持類(在JUnit 4.12或更高版本上受支持):

  • AbstractJUnit4SpringContextTests
  • AbstractTransactionalJUnit4SpringContextTests

AbstractJUnit4SpringContextTests是一個抽象的基本測試類,它集成了Spring TestContext框架和JUnit4環境中顯式的ApplicationContext測試支持。當您擴展AbstractJUnit4SpringContextTests時,您可以訪問一個受保護的applicationContext實例變量,您可以使用它來執行顯式的bean查找或測試整個上下文的狀態。

AbstractTransactionalJUnit4SpringContextTests是AbstractJUnit4SpringContextTests的抽象事務擴展,它爲JDBC訪問添加了一些方便的功能。這個類需要一個javax.sql。數據源bean和平臺事務管理器bean將在ApplicationContext中定義。當您擴展AbstractTransactionalJUnit4SpringContextTests時,您可以訪問一個受保護的jdbcTemplate實例變量,您可以使用它來運行SQL語句來查詢數據庫。您可以在運行與數據庫相關的應用程序代碼之前和之後使用這些查詢來確認數據庫狀態,Spring確保這些查詢在與應用程序代碼相同的事務範圍內運行。當與ORM工具一起使用時,一定要避免誤報。正如在JDBC測試支持中提到的,AbstractTransactionalJUnit4SpringContextTests還提供了方便的方法,這些方法使用前面提到的jdbcTemplate委託給JdbcTestUtils中的方法。此外,AbstractTransactionalJUnit4SpringContextTests提供了一個executeSqlScript(..)方法,用於在配置的數據源上運行SQL腳本。

注意:這些類爲擴展提供了方便。如果您不希望您的測試類被綁定到特定於Spring的類層次結構,您可以使用@RunWith(SpringRunner.class)或Spring的JUnit規則來配置您自己的自定義測試類。

SpringExtension for JUnit Jupiter
Spring TestContext框架提供了與JUnit Jupiter測試框架的完整集成,該框架是在JUnit 5中引入的。通過使用@ExtendWith(SpringExtension.class)註釋測試類,您可以實現標準的基於JUnit類的單元和集成測試,同時獲得TestContext框架的好處,例如支持加載應用程序上下文、測試實例的依賴注入、事務測試方法執行等等。

此外,由於JUnit Jupiter中豐富的擴展API, Spring在JUnit 4和TestNG支持的特性集之外還提供了以下特性:

  • 測試構造函數、測試方法和測試生命週期回調方法的依賴項注入。有關更多細節,請參見SpringExtension的依賴項注入。
  • 對基於SpEL表達式、環境變量、系統屬性等的條件測試執行的強大支持。有關更多細節和示例,請參閱Spring JUnit Jupiter中@EnabledIf和@DisabledIf的文檔,其中測試了註釋。
  • 自定義組成的註釋,結合了來自Spring和JUnit Jupiter的註釋。有關更多細節,請參見元註釋支持中的@TransactionalDevTestConfig和@TransactionalIntegrationTest示例。

下面的代碼清單展示瞭如何配置一個測試類,將SpringExtension與@ContextConfiguration結合使用:

// Instructs JUnit Jupiter to extend the test with Spring support.
@ExtendWith(SpringExtension.class)
// Instructs Spring to load an ApplicationContext from TestConfig.class
@ContextConfiguration(classes = TestConfig.class)
class SimpleTests {

    @Test
    void testMethod() {
        // execute test logic...
    }
}

由於您也可以使用JUnit 5中的註釋作爲元註釋,Spring提供了@SpringJUnitConfig和@SpringJUnitWebConfig組成的註釋,以簡化測試ApplicationContext和JUnit Jupiter的配置。
下面的例子使用@SpringJUnitConfig來減少前面例子中使用的配置數量:

// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load an ApplicationContext from TestConfig.class
@SpringJUnitConfig(TestConfig.class)
class SimpleTests {

    @Test
    void testMethod() {
        // execute test logic...
    }
}

類似地,下面的例子使用@SpringJUnitWebConfig創建一個WebApplicationContext來使用JUnit Jupiter:

// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load a WebApplicationContext from TestWebConfig.class
@SpringJUnitWebConfig(TestWebConfig.class)
class SimpleWebTests {

    @Test
    void testMethod() {
        // execute test logic...
    }
}

有關更多細節,請參見SpringJUnit Jupiter測試註釋中的@SpringJUnitConfig和@SpringJUnitWebConfig的文檔。

使用SpringExtension的依賴注入
SpringExtension實現了來自JUnit Jupiter的ParameterResolver擴展API,它讓Spring爲測試構造函數、測試方法和測試生命週期回調方法提供依賴注入。

具體地說,SpringExtension可以將來自測試的ApplicationContext的依賴注入到測試構造函數和方法中,這些構造函數和方法使用@BeforeAll、@AfterAll、@BeforeEach、@AfterEach、@Test、@RepeatedTest、@ParameterizedTest等進行註釋。

構造函數注入
如果一個特定的參數在木星JUnit測試類的構造函數的類型是ApplicationContext(或者一個亞種)或與@ autowired註解或meta-annotated, @ qualifier,或@ value,春天注入特定參數的值與相應的測試的ApplicationContext bean或價值。

如果測試類構造函數被認爲是自動可撤銷的,那麼Spring也可以配置爲自動裝配測試類構造函數的所有參數。如果滿足下列條件之一(按優先順序),則認爲構造函數是自動可撤銷的。

  • 構造函數用@Autowired標註。
  • @TestConstructor出現在測試類中,autowireMode屬性設置爲ALL。
  • 默認的測試構造函數autowire模式已更改爲ALL。

有關@TestConstructor的使用以及如何更改全局測試構造函數自動裝配模式的詳細信息,請參見@TestConstructor。

警告:如果測試類的構造函數被認爲是自動可撤銷的,那麼Spring將負責解析構造函數中所有參數的參數。因此,註冊到JUnit Jupiter的其他參數解析器無法解析這樣一個構造函數的參數。

如果@DirtiesContext用於在測試方法之前或之後關閉測試的ApplicationContext,那麼測試類的構造函數注入不能與JUnit Jupiter的@TestInstance(PER_CLASS)支持一起使用。

原因是@TestInstance(PER_CLASS)指示JUnit Jupiter在測試方法調用之間緩存測試實例。因此,測試實例將保留對最初從隨後關閉的ApplicationContext注入的bean的引用。因爲在這樣的場景中,測試類的構造函數只會被調用一次,所以依賴項注入將不會再次發生,並且後續的測試將與來自關閉的ApplicationContext的bean進行交互,這可能會導致錯誤。

要將@DirtiesContext與“測試方法之前”或“測試方法之後”模式與@TestInstance(PER_CLASS)結合使用,必須配置Spring中的依賴項,通過字段或setter注入來提供,以便在測試方法調用之間重新注入。

在下面的示例中,Spring將從TestConfig.class加載的ApplicationContext中注入OrderService bean到OrderServiceIntegrationTests構造函數中。

@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    private final OrderService orderService;

    @Autowired
    OrderServiceIntegrationTests(OrderService orderService) {
        this.orderService = orderService;
    }

    // tests that use the injected OrderService
}

請注意,該特性允許測試依賴關係是最終的,因此是不可變的。
如果spring.test.constructor.autowire。mode屬性爲all(參見@TestConstructor),我們可以省略前面示例中構造函數的@Autowired聲明,結果如下。

@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    private final OrderService orderService;

    OrderServiceIntegrationTests(OrderService orderService) {
        this.orderService = orderService;
    }

    // tests that use the injected OrderService
}

方法注射
如果一個參數在木星JUnit測試方法或測試生命週期回調方法的類型是ApplicationContext(或者一個亞種)或與@ autowired註解或meta-annotated, @ qualifier,或@ value,春天注入特定參數的值與相應的測試的ApplicationContext bean。

在下面的例子中,Spring將從TestConfig.class中加載的ApplicationContext中的OrderService注入到deleteOrder()測試方法中:

@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    @Test
    void deleteOrder(@Autowired OrderService orderService) {
        // use orderService from the test's ApplicationContext
    }
}

由於JUnit Jupiter中對參數解析器支持的健壯性,您還可以將多個依賴項注入到單個方法中,這些依賴項不僅來自Spring,還來自JUnit Jupiter本身或其他第三方擴展。
下面的例子展示瞭如何讓Spring和JUnit Jupiter同時將依賴項注入placeorderrepeat()測試方法。

@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    @RepeatedTest(10)
    void placeOrderRepeatedly(RepetitionInfo repetitionInfo,
            @Autowired OrderService orderService) {

        // use orderService from the test's ApplicationContext
        // and repetitionInfo from JUnit Jupiter
    }
}

注意,使用來自JUnit Jupiter的@RepeatedTest可以讓測試方法訪問RepetitionInfo。
TestNG支持類
org.springframework.test.context。testng包爲基於testng的測試用例提供了以下支持類:

  • AbstractTestNGSpringContextTests

  • AbstractTransactionalTestNGSpringContextTests

AbstractTestNGSpringContextTests是一個抽象的基本測試類,它在TestNG環境中集成了Spring TestContext框架和顯式的ApplicationContext測試支持。當您擴展AbstractTestNGSpringContextTests時,您可以訪問受保護的applicationContext實例變量,您可以使用它來執行顯式的bean查找或測試整個上下文的狀態。

AbstractTransactionalTestNGSpringContextTests是AbstractTestNGSpringContextTests的抽象事務擴展,它爲JDBC訪問添加了一些方便的功能。這個類需要一個javax.sql。數據源bean和平臺事務管理器bean將在ApplicationContext中定義。在擴展AbstractTransactionalTestNGSpringContextTests時,可以訪問受保護的jdbcTemplate實例變量,可以使用該變量執行SQL語句來查詢數據庫。您可以在運行與數據庫相關的應用程序代碼之前和之後使用這些查詢來確認數據庫狀態,Spring確保這些查詢在與應用程序代碼相同的事務範圍內運行。當與ORM工具一起使用時,一定要避免誤報。正如在JDBC測試支持中提到的,AbstractTransactionalTestNGSpringContextTests還提供了方便的方法,這些方法使用前面提到的jdbcTemplate委託給JdbcTestUtils中的方法。此外,AbstractTransactionalTestNGSpringContextTests提供了一個executeSqlScript(..)方法,用於在配置的數據源上運行SQL腳本。

注意:這些類爲擴展提供了方便。如果您不希望您的測試類綁定到一個spring特定類層次結構,您可以配置自己的自定義測試類通過使用@ContextConfiguration, @TestExecutionListeners等等,通過手動TestContextManager插裝您的測試類。有關如何測試類的示例,請參閱AbstractTestNGSpringContextTests的源代碼。

 

發佈了23 篇原創文章 · 獲贊 0 · 訪問量 482
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章