1. 程式人生 > >Spring framework testing文件讀書筆記

Spring framework testing文件讀書筆記

該文章是我讀Spring testing英文官方文件的讀書筆記,方便以後快速的回憶文件裡講述的內容,而不用再去讀一遍官方文件。 文章內容精簡掉了官方文件的一些比較淺顯易懂的用法以及一些很細節的地方,一半是翻譯,然後加入部分自己的理解,可以使讀者快速的瞭解Spring testing提供了些什麼功能以及在什麼情況下如何使用。

基於的Spring文件是Spring framework的testing模組,版本5.0.9。 官方網站https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/testing.html

Chapter 2. Unit Testing(單元測試)

2.1 Mock Objects (mock物件)

2.1.1 Environment(環境)

org.springframework.mock.env包下包含了EnvironmentPropertySource的實現,MockEnvironmentMockPropertySource可用於那些執行在容器外,但是又依賴environment和properties的test。

2.1.2 JNDI

org.springframework.mock.jndi包下包含了JNDI SPI的實現。

2.1.3 Servlet API

org.springframework.mock.web包下包含了一系列的Servlet API mock物件,可用於測試web context、controller以及filter,這些mock物件比一些其他的動態mock物件(比如EasyMock)或者Servlet API mock物件(比如MockObjects)更適合與Spring Web MVC框架一起使用。

2.1.4 Spring Web Reactive

org.springframework.mock.http.server.reactive包下包含了ServerHttpRequestServerHttpResponse的mock實現,可用於WebFlux應用。org.springframework.mock.web.server包下包含了ServerWebExchange的mock實現,這個實現依賴於前面兩個request和response的mock物件。

MockServerHttpRequestMockServerHttpResponse都實現了想相同的抽象類。

2.2 Unit Testing support Classes

2.2.1 General testing utilities(常用的測試工具類)

org.springframework.test.util包下包含了一些可用於單元測試或者整合測試的常用工具類。

ReflectionTestUtils類包含了一系列基於反射的工具方法。開發者可以使用它來改變常量的值、設定一個非public的屬性值、呼叫一個非public的setter方法、呼叫一個非public的配置的或者生命週期回撥函式等測試場景,這些場景可能發生在一下這些情況:

  • 在使用JPA和Hibernate等ORM框架時訪問實體類的非public的setter方法時。
  • Spring對@Autowired@Inject、和@Resource等註解的支援,使之可以讓依賴注入到private和protected的field、setter方法以及配置方法上。
  • @PostConstruct@PreDestro等生命週期函式的使用上。

AopTestUtils包含了一系列基於AOP的工具方法。這些方法可以用來獲取隱藏在一個或多個Spring代理之後的目標物件的引用。比如,如果我們配置了一個使用了EasyMock或者Mockito的動態mock的bean,且這個mock物件被Spring代理包裝了,而我們又想直接訪問該mock物件去配置其expectation以及執行驗證。想了解更多的AOP工具類,可以參考AopUtilsAopProxyUtils的javadoc。

2.2.2 Spring MVC

org.springframework.test.web包下包含了ModelAndViewAssert,可以使用它來對Spring MVC的ModelAndView物件進行單元測試。

Chapter 3 Integration Testing(整合測試)

3.1 Overview

整合測試可以幫助我們測試下列場景,而不需要依賴具體的應用服務其或者環境:

  • 在Spring IOC容器中正確裝配bean。
  • 基於JDBC或者ORM框架的資料訪問。

3.2 Goals of Integration Testing(整合測試的目標)

  • 管理多個測試執行期間的Spring IOC容器快取(Spring Ioc container caching)
  • 提供test fixture例項的依賴注入(Dependency Injection of test fixture instances)
  • 提供整合測試的事務管理(transaction management)
  • 支援用於協助開發者編寫繼承測試的Spring基礎類(Spring-specific base classes)

3.2.1 Context management and caching(上下文管理和快取)

Spring的TestContext框架提供了對ApplicationContextWebApplicationContext載入的一致性,以及快取這些context。將context快取起來是很有必要的,這樣再執行的多個test case的時候,就不用多次載入資源。比如一個專案有50到100個Hibernate對映檔案,那麼在執行test case是,可能就需要10到20秒鐘去載入它們,如果每執行一個test case就話10到20秒去載入資源,那麼這樣會大大降低開發者的效率。

預設情況下,TestContext載入一次ApplicationContext後,可以將其快取起來,每一個test suite中的所有test case,可以共同使用。每一個test suite表示執行在同一個JVM裡的test case,比如執行在同一個Ant、Maven或者Gradle裡的test case。但是,如果修改了bean definition,那麼TestContext框架就會重新載入資源。

3.2.2 Dependency Injection of test fixture(test fixture的依賴注入)

在使用TestContext框架載入ApplicationContext時,我們可以選擇通過依賴注入配置測試類的例項。

3.2.3 Transaction management(事務管理)

如果使用真實的資料庫來測試持久層,可能會導致真實資料的變化,即使是使用開發資料庫來執行測試,那麼如果修改了某些資料,也可能影響到還未執行的test case的結果。

預設情況下,我們不用寫額外的程式碼,TestContext就會為每一個test case建立以及回滾事務。這是通過定義在測試aplication context裡的PlatformTransactionManagerbean來實現的。

如果我們想提交一個事務(雖然很少見),我們可以使用@Commit註解來在執行完test case後提交事務。

3.2.4 Support classes for integration testing(繼承測試的支援類)

Spring TestContext框架提供了一些抽象的支援類來簡化編寫繼承測試。這些類可以讓我們來訪問ApplicationContext以及JdbcTemplate

3.3 JDBC Testing Support(JDBC測試支援)

org.springframework.test.jdbc包裡的JdbcTestUtils提供了一系列的JDBC相關的工具方法,可用來簡化標準資料庫的測試場景,比如如下方法:

  • countRowsInTable(..): 計算指定table的行數
  • countRowsInTableWhere(..): 使用指定的WHERE語句計算指定table的行數
  • deleteFromTables(..): 刪除指定table的所有行
  • deleteFromTableWhere(..): 使用指定的WHERE語句刪除指定table的行
  • dropTables(..): dorp指定的table

3.4 Annotations

Spring框架提供了許多可以用於單元測試或者整合測試的註解。

3.4.1 Spring Testing Annotations(Spring測試註解)

@BootstrapWith

@BootstrapWith是一個類級別的註解,可以配置Spring testContext如何啟動。可用於指定一個自定義的TestContextBootstrapper。檢視Bootstrapping the TestContext framework章節可獲得更多資訊。

@ContextConfiguration

@ContextConfiguration是類級別的註解,可以用於決定如何載入和配置整合測試的ApplicationContext。通常用於指定xml配置檔案的路徑或者註解配置檔案的class。

@ContextConfiguration("/test-config.xml")
public class XmlApplicationContextTests {
  // class body...
}
@ContextConfiguration(classes= TestConfig.class)
public class ConfigClassApplicationContextTests {
 // class body...
}

也可以用來指定一個ApplicationContextInitializer類:

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

還可以指定ContextLoader

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

@WebAppConfiguration

@WebAppConfiguration是類級別的註解,用於WebApplicationContext的整合測試。該註解僅有的作用就是確保WebApplicationContext會被載入,而且在預設情況下使用"file:src/main/webapp"指定web應用的根路徑,該路徑會被用於建立一個MockServletContext,作為WebApplicationContextServletContext

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

可以為@WebAppConfiguration註解的value屬性設定值,以此來指定自定義的資源路徑,value的值可以是"classpath:""file:"字首,如果不指定則預設是使用檔案系統的資源。

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

注意@WebAppConfiguration必須要和@ContextConfiguration一起使用。

@ContextHierarchy

@ContextHierarchy是類級別的註解,可以為ApplicationContext的整合測試定義一個層級關係,具體請見Context hierarchies章節。

@ActiveProfiles

@ActiveProfiles是類級別的註解,可以指定哪個bean definition profile是active狀態的。

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

更多使用方法請見Context configuration with environment profiles章節。

@TestPropertySource

@TestPropertySource是類級別的註解,可用於指定property檔案的路徑或者指定內聯的property。它會新增測試環境的property到EnvironmentPropertySources集合中。

相對於作業系統的環境變數、Java系統的property或者通過@PropertySource註解指定的應用程式property,測試環境的property有更高的優先順序。而內聯的property擁有更高的優先順序。

下面是通過檔案路徑指定property檔案:

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

下面是宣告內聯的property:

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

@DirtiesContext

@DirtiesContext可用於在特定的階段將``````ApplicationContext標記為dirtied(髒的?)並且將其快取清除。比如某一些test case會改變單例類的狀態,那麼將其標記為dirtied,可以在其執行後重新載入ApplicationContext```,從而恢復被修改的狀態。

該註解可以用在類上,也可以用在方法上,並且通過配置methodModeclassMode屬性,指定是在“之前”還是“之後”將ApplicationContext標記為dirtied。

當用在類上時,可以為classMode屬性指定BEFORE_CLASSAFTER_CLASSBEFORE_EACH_TEST_METHODAFTER_EACH_TEST_METHOD四個值,其中AFTER_CLASS是預設值。當用在方法上時,可以為methodMode屬性指定BEFORE_METHOD和AFTER_METHOD兩個值,其中AFTER_METHOD是預設值。

@DirtiesContext@ContextHierarchy一起使用時,可以為@DirtiesContexthierarchyMode屬性指定值來控制如何清理快取,具體請見DirtiesContext.HierarchyMode的javadocs。

@TestExecutionListeners

@TestExecutionListeners是類級別的註解,可以用來配置TestExecutionListener介面的實現類,這些實現類會被TestContextManager註冊。

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

@Commit

@Commit可以用在類上,也可以用在方法上,用來指定一個被事務管理的test case在執行後需要提交。其功能相當於@Rollback(false)

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

@Rollback

@Rollback可以用在類上,也可以用在方法上,用於指定一個test case的事務是否回滾,如果是true則表示回滾,false表示不回滾(等同於@Commit),預設為true。而且即使不指定@Rollback註解,預設情況下Spring TestContext框架也會回滾每一個test case的事務。

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

@BeforeTransaction和@AfterTransaction

@BeforeTransaction@AfterTransaction註解可以用在返回型別為void的方法上,表示在開啟一個事務之前或者之後去執行這個方法。

@BeforeTransaction
void beforeTransaction() {
  // logic to be executed before a transaction is started
}
@AfterTransaction
void afterTransaction() {
  // logic to be executed after a transaction has ended
}

@Sql

@Sql註解可以在類上,也可以用在方法上,用於指定一個SQL指令碼,並在test case之前執行這個指令碼。

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

更多細節可以參考“Executing SQL scripts declaratively with @Sql”章節。

@SqlConfig

@SqlConfig需要和@Sql一起使用,用於指定如何解析以及執行SQL指令碼。

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

@SqlGroup

@SqlGroup註解可以用來聚集多個@Sql註解。

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

3.4.2 Standard Annotation Support(標準註解的支援)

下面列出的這些註解,都是Spring的標準註解,在Spring TestContext框架中,可以像在普通情況下使用它們一樣使用:

  • @Autowired
  • @Qualifier
  • @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

但是,像@PostConstruct@PreDestroy這樣的生命週期方法,在測試類中使用會受到一定的限制。如果一個測試類中的一個方法被標記了@PostConstruct,那麼它會在所有的before方法之前被執行。而如果一個方法被標記了@PreDestroy,那麼它根本不會被執行。

3.4.3 Spring JUnit 4 Testing Annotations(Spring JUnit 4測試註解)

以下這些註解只能和“SpringRunner”、“Spring’s JUnit 4 rules”或者“Spring’s JUnit 4 support classes”一起使用。

@IfProfileValue

@IfProfileValue註解可以指明被標記的test case在某一個特定的測試環境下才可用。該註解可以指定namevalue鍵值對,當指定的鍵值對與配置好的ProfileValueSource返回的相應鍵值對能夠匹配上,該test case才可用,否則該test case會被忽略。

@IfProfileValue註解可以使用在類或者方法上,使用在類上時比使用在方法上有更高的優先順序。如果同時使用在類上和方法上,那麼需要這兩處的鍵值對都能匹配,該test case才是可用的。如果某個test case不使用@IfProfileValue則表示其是可用的。該註解與JUnit 4的@Ignore註解有相似的作用,@Ignore表示某個test case總是不可用的。

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

也可以指定多個value:

@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是類級別的註解,可以指定具體型別的ProfileValueSource,用於獲得profile。如果不使用該註解,則預設情況下使用的ProfileValueSourceSystemProfileValueSource

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

@Timed

@Timed是方法級別的註解,表示test case需要在指定時間內(毫秒)執行完成,否則實行失敗。指定的時間包括該測試用例本身,以及任何重複測試(參考@Repeat)、任何set up或者tear down步驟。

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

Spring的@Timed註解和JUnit4的@Test(timeout=...)有一點區別。Spring的@Timed註解,在超時後還會等待該test case執行完成在返回失敗,但是JUnit4的@Test(timeout=...)會在超時後直接返回失敗,原因是JUnit4是在不同的執行緒中執行test case。

@Repeat

@Repeat是方法級別的註解,表示該test case會被重複執行多次。重複執行的內容除了該test case本身,還包括任何的set up和tear down步驟。

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

3.4.4 Spring JUnit Jupiter Testing Annotations

以下的註解都只能執行在“SpringExtension”或者JUnit Jupiter(JUnit5)中,就不過多講解。

  • @SpringJUnitConfig
  • @SpringJUnitWebConfig
  • @EnabledIf
  • @DisabledIf

3.4.5 Meta-Annotation Support for Testing(測試的元註解支援)

以下這些註解,都可以作為元註解標記到使用者自定義的註解上,達到簡化程式碼的目的。

  • @BootstrapWith
  • @ContextConfiguration
  • @ContextHierarchy
  • @ActiveProfiles
  • @TestPropertySource
  • @DirtiesContext
  • @WebAppConfiguration
  • @TestExecutionListeners
  • @Transactional
  • @BeforeTransaction
  • @AfterTransaction
  • @Commit
  • @Rollback
  • @Sql
  • @SqlConfig
  • @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)
  • @EnabledIf(only supported on JUnit Jupiter)
  • @DisabledIf(only supported on JUnit Jupiter)

例如,下面的配置,可能會重複的使用在多個test suite上:

@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 { }

我們可以通過引入一個使用者自定義的註解:

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

然後將其配置到test suite上:

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

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

3.5 Spring TestContext Framework(Spring TestContext框架)

Spring TestContext框架(位於org.springframework.test.context包)提供了對泛型、註解驅動的支援。

3.5.1 Key abstractions(關鍵抽象)

Spring TestContext框架的核心由TestContextManager類,以及TestContextTestExecutionListenerSmartContextLoader介面組成。每一個test類會建立一個TestContextManager,而TestContextManager管理著一個TestContextTestContext儲存了當前test case的上下文。TestContextManager還會更新TestContext的狀態,並且委託給TestExecutionListener的實現,該實現提供了依賴注入、事務管理等功能。SmartContextLoader負責為test類載入ApplicationContext

TestContext

TestContext封裝了一個被執行的test cast的上下文,為其負責的測試例項提供了對上下文的管理以及快取支援,而且它對實際使用的測試框架沒有感知。TestContext會委託SmartContextLoader去載入它需要的ApplicationContext

TestContextManager

TestContextManager是Spring TestContext框架的主要入口點,負責管理TestContext,以及在具體的執行點通知事件給已經註冊的TestExecutionListener

TestExecutionListener

TestExecutionListener定義了監聽TestContextManager事件的API。

Context Loaders

ContextLoader是在Spring2.5引入的載入ApplicationContext的策略介面,在Spring3.1有引入了功能更強大的SmartContextLoader介面。SmartContextLoader介面提供了對註解類、active profile、test property source、context hierarchy以及WebApplicationContext的支援。Spring提供了很多SmartContextLoader介面的實現類,這裡就不一一介紹。

3.5.2 Bootstrapping the TestContext framework(啟動TestContext框架)

雖然預設的配置已經使用與大多數情況,但是有時開發者會遇到修改ContextLoader,實現自定義的Context等情況,這時TestContext框架的啟動就需要做一點調整。Spring為TestContext框架的啟動提供了TestContextBootstrapper介面作為SPI。一個TestContextBootstrapper會被TestContextManager用於載入TestExecutionListener的實現類以及建立TestContext。我們可以使用@BootstrapWith註解指定使用者自定義的啟動策略類,如果不指定,那麼框架會根據是否使用了@WebAppConfiguration決定使用DefaultTestContextBootstrapper還是WebTestContextBootstrapper

3.5.3 TestExecutionListener configuration(TestExecutionListener配置)

Spring預設提供並註冊了以下TestExecutionListener介面的實現:

  • ServletTestExecutionListener: 為WebApplicationContext配置了Servlet API的mock
  • DirtiesContextBeforeModesTestExecutionListener: 處理@DirtiesContext註解的before模式
  • DependencyInjectionTestExecutionListener: 為test例項提供依賴注入
  • DirtiesContextTestExecutionListener: 處理@DirtiesContext註解的after模式
  • TransactionalTestExecutionListener: 為事務執行提供預設的回滾策略
  • SqlScriptsTestExecutionListener: 執行由@Sql註解提供的SQL指令碼

Registering custom TestExecutionListeners(註冊自定義TestExecutionListener)

通過@TestExecutionListeners註解可以註冊自定義的TestExecutionListener

Automatic discovery of default TestExecutionListeners(自動發現預設的TestExecutionListener)

Spring4.1之後,還提供了SpringFactoriesLoader機制來自動發現TestExecutionListener。Spring在spring-test module的META-INF/spring.factories檔案中就列出了所有預設的TestExecutionListener。使用者也可以在自己工程的ETA- INF/spring.factories檔案中提供需要自動發現的TestExecutionListener。這其實就是SPI的使用方式。

Ordering TestExecutionListeners(TestExecutionListener的順序)

通過前面提到的SpringFactoriesLoader機制實現的自動發現TestExecutionListener的能力,Spring還提供了Ordered介面和@Order註解來指定其順序。使用者自定義的TestExecutionListener可以通過為上述介面或者註解指定值來指定TestExecutionListener的順序。

Merging TestExecutionListeners(合併TestExecutionListener)

如果使用者通過@TestExecutionListeners註解註冊了自定義的TestExecutionListener,那麼前面提到的預設的TestExecutionListener就不會再被註冊,這會導致開發者需要手動指定上述預設的listener:

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

可以為@TestExecutionListeners註解的mergeMode屬性指定MergeMode.MERGE_WITH_DEFAULTS來告訴框架將當前listener與預設的listener合併。

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

3.5.4 Context management(上線文管理)

Context configuration with XML resources(使用XML資原始檔配置上下文)

@ContextConfiguration註解的locations屬性設定值,可以指定載入ApplicationContext的xml檔案的路徑。路徑格式類似context.xml的,表示類路徑下當前檔案的相對路徑的資源;/org/example/config.xml這種格式表示類路徑下絕對路徑的資源。

例子:

@RunWith(SpringRunner.class)
@ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"})
public class MyTest {
  // class body...
}

也可以把locations屬性值省略:

@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-config.xml"})
public class MyTest {
  // class body...
}

如果locationsvalue屬性都省略,那麼框架會使用GenericXmlContextLoader或者GenericXmlWebContextLoader去自動定位資原始檔,定位規則為:如果我們的類叫做com.example.MyTest,那麼GenericXmlContextLoader會載入classpath:com/example/MyTest-context.xml路徑下的資原始檔。

Context configuration with annotated classes(使用註解類配置上下文)

可以為@ContextConfiguration註解提供被註解的配置類來指定ApplicationContext資源:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {AppConfig.class, TestConfig.class})
public class MyTest {
  // class body...
}

Context Configuration with Context Initializers(使用Context Initializer配置上下文)

通過給@ContextConfiguration註解的initializers屬性賦值,可以為我們的test case指定context initializer來配置ApplicationContextinitializers屬性的值是ApplicationContextInitializer的實現類的陣列。

@RunWith(SpringRunner.class)
@ContextConfiguration(
    classes = TestConfig.class,
initializers = TestAppCtxInitializer.class) 
public class MyTest {
    // class body...
}

Context Configuration Inheritance(繼承上下文配置)

可以為@ContextConfiguration註解的inheritLocationsinheritInitializers屬性指定boolean值來表示當前test類是否繼承父類的配置,預設值是true。可繼承的配置包括resouce路徑、基於註解的配置類、context initializer。

Context Configuration with Environment Profiles(使用環境profile配置context)

使用@ActiveProfiles註解,可以為test類指定一系列可用的profile,下面的程式碼就指定了使用dev的profile。

@RunWith(SpringRunner.class)
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
public class TransferServiceTest {

    @Autowired
    private TransferService transferService;

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

還可以給@ActiveProfiles註解的inheritProfiles屬性指定boolean值,表示是否繼承父類可用profile。下面的程式碼表示不繼承:

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

有時候,我們會需要通過程式設計來指定可用的profile而不是通過宣告,這時候,我們可以提供一個ActiveProfilesResolver介面的實現類並且將其賦值到@ActiveProfiles註解的resolver屬性上來實現程式設計式指定可用profile,下面是一個例子:

@ActiveProfiles(
    resolver = OperatingSystemActiveProfilesResolver.class,
    inheritProfiles = false)
public class TransferServiceTest extends AbstractIntegrationTest {
    // test body
}
public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver {

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

Context configuration with test property sources(使用test property source配置上下文)

使用@TestPropertySource註解可以為test類配置測試環境下使用的property資原始檔。

給該註解的locations或者value屬性賦值,可以指定資原始檔的路徑,每一個資原始檔都會被解析為一個Spring的Resource。帶斜槓的路徑表示類路徑下的絕對路徑,例如:/org/example/test.xml;不帶斜槓的表示在類路徑下,相對於當前檔案的相對路徑,例如:"test.properties",還可以為路徑指定字首(classpath:, file:, http:等)。路徑中不支援萬用字元,比如*/.properties,必須明確指定資源的完整路徑。

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

@TestPropertySource還可以指定內聯的property鍵值對。內聯的property相對於通過資原始檔新增的property擁有更高的優先順序,可以使用下列三種形式表示:

  • “key=value”
  • “key:value”
  • “key value”
@ContextConfiguration
@TestPropertySource(properties = {"timezone = GMT", "port: 4242",})
public class MyIntegrationTests {
  // class body...
}

通過給@TestPropertySource註解的inheritLocationsinheritProperties屬性設定boolean值,可以指定是否繼承父類的property資原始檔或者內聯的property,預設值是true。

Loading a WebApplicationContext(載入WebApplicationContext)

@WebAppConfiguration註解可以用來載入WebApplicationContext,這個在前面的章節講過一些基礎用法,這裡講解一寫它的額外功能。

ServletTestExecutionListener為web測試提供了統一的支援,它在每一個測試方法開始之前,通過RequestContextHolder設定了預設的thread local的狀態,並且基於@WebAppConfiguration配置的資源建立了MockHttpServletRequestMockHttpServletResponseServletWebRequest,而且保證了MockHttpServletResponseServletWebRequest會被注入到test例項中,在test執行完成後再將其thread local的狀態清除。

下面的例子展示了哪些mock可以被自動裝配到test例項中,其中WebApplicationContextMockServletContext在整個test suite中都會被快取起來,而別的則不會:

@WebAppConfiguration
@ContextConfiguration
public class WacTests {

    @Autowired
    WebApplicationContext wac; // cached
    
    @Autowired
    MockServletContext servletContext; // cached
    
    @Autowired
    MockHttpSession session;
    
    @Autowired
    MockHttpServletRequest request;
    
    @Autowired
    MockHttpServletResponse response;
    
    @Autowired
    ServletWebRequest webRequest;
    //...
}

Context caching(上下文快取)

TestContext framework會將載入過的ApplicationContext或者WebApplicationContext快取起來,供同一個test suite中的其他test case使用。Spring會使用下列配置的引數作為快取context的key:

  • 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指定了@ContextConfiguration(locations={"app-config.xml", "test-config.xml"}),那麼框架會唯一的使用一個key將他們快取在一個static上下文快取裡。這時,如果TestClassB也使用了同樣的註解以及值,並且沒有定義@WebAppConfiguration,沒有使用不同的ContextLoader、可用profile、context initializer、測試property source以及parent context,那麼框架會在這兩個類之間共享ApplicationContext```。

預設情況下,框架最多隻會快取32個context,可以設定spring.test.context.cache.maxSizeJVM系統變數的值來改變快取大小。也可以通過程式設計式的使用SpringPropertiesAPI去設定同樣的屬性的值。

Context hierarchies(上下文繼承)

這章和之前講解@ContextHierarchy註解的章節內容差不多,額外講了一些細節,這裡不再單獨講解。

3.5.5 Dependency injection of test fixtures(test fixture的依賴注入)

該章節介紹了在test類中可以使用@Autowired等註解達到依賴注入的功能,額外講了一些細節,這裡不再單獨講解。

3.5.6 Testing request and session scoped beans(測試request和session作用域的bean)

該章節設計到web相關的內容,先暫時不講解,有需求的時候再研究。

3.5.7 Transaction management(事務管理)

TestContext框架預設會註冊TransactionalTestExecutionListener,以此來管理事務。但是除此之外,在我們的測試類里加載的ApplicationContext裡還必須配置一個PlatformTransactionManagerbean,並且需要在test方法或者test類上使用@Transactional註解。

Enabling and disabling transactions(啟用/禁用事務)

@Transactional使用到test方法上,則表示該方法被事務管理。將@Transactional使用在test類上,則表示該類以及其子類的所有方法都可以被事務管理。如果一個方法或者其所在的類(包括父類)沒有被@Transactional註解標記,那麼它不會被事務管理。注意,即使使用了@Transactional註解,但是如果傳播級別是NOT_SUPPORTED,那麼該方法也不會被事務管理。

Transaction rollback and commit behavior(事務的回滾和提交行為)

預設情況下,一個test事務會在方法執行完成後回滾,如果我們想要提交事務,那麼需要使用@Commit或者@Rollback註解。

Programmatic transaction management(程式設計式事務管理)

使用TestTransaction,可以實現test程式設計式事務管理。暫時用不上,這裡不再講解。

Executing code outside of a transaction(在事務外執行額外程式碼)

有時候我們需要在事務開始或者結束之後執行一些額外的程式碼,這時可以在返回值為void的方法上使用@BeforeTransaction或者@AfterTransaction註解,那麼TransactionalTestExecutionListener就會在事務開始或結束後執行被標記的方法。

Configuring a transaction manager(配置事務管理器)

TransactionalTestExecutionListener需要載入的ApplicationContext裡定義一個PlatformTransactionManagerbean,如果存在多個PlatformTransactionManagerbean,那麼可以使用@Transactional("myTxMgr")或者@Transactional(transactionManager = "myTxMgr")指定需要使用的bean。或者可以讓被@Configuration標記的類實現TransactionManagementConfigurer介面,詳情請見TestContextTransactionUtils.retrieveTransactionManager()

3.5.8 Executing SQL scripts(執行SQL指令碼)

Executing SQL scripts programmatically(使用程式設計式的方式執行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指令碼的解析以及執行的情況。

ResourceDatabasePopulator提供了一套基於物件的API,這套API使用外部的SQL指令碼來程式設計式的填充、初始化或者清理資料庫。並且還提供了配置字元編碼、statement分隔符、comment分隔符以及錯誤處理標記,每一個配置都有預設的值。下面是例子:

@Test
public 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指令碼的。

類似的,AbstractTransactionalJUnit4SpringContextTestsAbstractTransactionalTestNGSpringContextTests在內部也是使用ResourceDatabasePopulator執行SQL指令碼的。

Executing SQL scripts declaratively with @Sql(使用@Sql宣告式執行SQL指令碼)

SqlScriptsTestExecutionListener提供了對@Sql的支援,使用@Sql註解可以宣告式執行SQL指令碼。

Path resource semantics(路徑資源語義用法)

任何一個path都會被解析為Spring的Resource。帶斜杆開頭的路徑表示從類路徑開始的絕對路徑,例如:"/org/example/schema.sql",不帶斜杆開頭的表示從當前檔案開始的相對路徑,例如:"schema.sql"。還可以為路徑加上字首,這樣Spring會使用相應的協議載入資源,比如classpath:file:http:

下面是一個例子:

@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
    }   
}
Default script detection(預設的指令碼發現策略)

如果@Sql沒有明確的指定SQL指令碼,框架會嘗試去尋找預設的指令碼,如果找不到,則會丟擲IllegalStateException異常。

  • 註解宣告在類上:如果test類名是com.example.MyTest,那麼需要指令碼的路徑是"classpath:com/example/MyTest.sql"
  • 註解宣告在方法上:如果該方法是com.example.MyTest#testMethod(),那麼需要的指令碼的路徑是"classpath:com/example/MyTest.testMethod.sql"
Declaring multiple @Sql sets(宣告多個@Sql)

有時候我們需要指定多個SQL指令碼,並且多個SQL指令碼之間有不同的配置、不同的錯誤處理規則、不同的執行階段等。這時候我們需要宣告多個@Sql,如果是JAVA8,那麼可以之間宣告多個@Sql註解,否則需要使用@SqlGroup

下面是例子:

@Test
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`"))
@Sql("/test-user-data.sql")
public void userTest {
  // execute code that uses the test schema and test data
}
@Test
@SqlGroup({
  @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
  @Sql("/test-user-data.sql")
)}
public void userTest {
  // execute code that uses the test schema and test data
}
Script execution phases(指令碼執行階段)

預設情況下,SQL指令碼都會在test方法之前被執行。可以通過為@Sql註解的executionPhase屬性設定AFTER_TEST_METHOD,讓SQL指令碼在test方法執行完成之後再執行。下面的例子中,ISOLATEDAFTER_TEST_METHOD來自Sql.TransactionModeSql.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
)
public void userTest {
  // execute code that needs the test data to be committed
  // to the database outside of the test's transaction
}
Script configuration with @SqlConfig(使用@SqlConfig配置指令碼)

通過@Sql註解的config屬性設定的配置,只對當前被註解的方法有效。

我們可以使用@SqlConfig註解全域性配置SQL指令碼,@SqlConfig需要使用在test類上,這時該test類以及其子類,都會繼承其配置。每一個@SqlConfig的屬性都有一個預設值,如果子類需要覆蓋父類的某個屬性為預設值,那麼可以將屬性的值謝偉""獲得DEFAULT

Transaction management for @Sql(@Sql的事務管理)

@Sql裡的指令碼也可以被事務管理起來。還可以為@SqlConfig註解的屬性dataSourcetransactionManager設定值來指定資料來源和事務管理器。

@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.");
    }
}

3.5.10 TestContext Framework Support Classes(TestContext框架支援類)

Spring JUnit 4 Runner

Spring提供了與JUnit4完全整合的自定義runner(支援JUnit 4.12以及更高版本),使用者只用使用@RunWith(SpringJUnit4ClassRunner.class)或者@RunWith(SpringRunner.class)註解標記test類即可。

下面是一個最簡化的例子,展示如何使用自定義的runner配置test類:

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

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

注意,@TestExecutionListeners配置了一個空的list,禁用了所有預設的listener。

Spring JUnit 4 Rules

org.springframework.test.context.junit4.rules包提供了JUnit4的rule:

  • SpringClassRule
  • SpringMethodRule
    SpringClassRule是一個JUnitTestRule,支援類級別的特性。SpringMethodRule是一個JUnitMethodRule,支援例項級別以及方法級別的特性。

Spring的基於rule的JUnit支援,擁有所有SpringRunner的優點,而且可以和一些其他的runner配合使用,比如Junit4的Parameterized以及MockitoJunitRunner。下面是例子:

 // 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包為基於JUnit4的test類提供了以下支援類:

  • AbstractJUnit4SpringContextTests
  • AbstractTransactionalJUnit4SpringContextTests

AbstractJUnit4SpringContextTests是一個抽象類,test類繼承它後,可以訪問protected applicationContext例項變數來實現查詢bean等功能。

AbstractTransactionalJUnit4SpringContextTestsAbstractJUnit4SpringContextTests的基礎上添加了一些訪問JDBC的功能。

SpringExtension for JUnit Jupiter

這一章節是JUnit5的內容,待。

用到了@ExtendWith(SpringExtension.class)@SpringJUnitConfig

Dependency Injection with SpringExtension

這一章節也是JUnit5的內容,待。

3.6 Spring MVC Test Framework(Spring MVC測試框架)

待。

3.7 WebTestClient

待。

3.8 PetClinic Example

待。