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
包下包含了Environment
和PropertySource
的實現,MockEnvironment
和MockPropertySource
可用於那些執行在容器外,但是又依賴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
包下包含了ServerHttpRequest
和ServerHttpResponse
的mock實現,可用於WebFlux應用。org.springframework.mock.web.server
包下包含了ServerWebExchange
的mock實現,這個實現依賴於前面兩個request和response的mock物件。
MockServerHttpRequest
和MockServerHttpResponse
都實現了想相同的抽象類。
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工具類,可以參考AopUtils
和AopProxyUtils
的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框架提供了對ApplicationContext
和WebApplicationContext
載入的一致性,以及快取這些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
,作為WebApplicationContext
的ServletContext
。
@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到Environment
的PropertySources
集合中。
相對於作業系統的環境變數、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```,從而恢復被修改的狀態。
該註解可以用在類上,也可以用在方法上,並且通過配置methodMode
和classMode
屬性,指定是在“之前”還是“之後”將ApplicationContext
標記為dirtied。
當用在類上時,可以為classMode屬性指定BEFORE_CLASS
、AFTER_CLASS
、BEFORE_EACH_TEST_METHOD
、AFTER_EACH_TEST_METHOD
四個值,其中AFTER_CLASS
是預設值。當用在方法上時,可以為methodMode
屬性指定BEFORE_METHOD和AFTER_METHOD
兩個值,其中AFTER_METHOD
是預設值。
當@DirtiesContext
和@ContextHierarchy
一起使用時,可以為@DirtiesContext
的hierarchyMode
屬性指定值來控制如何清理快取,具體請見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在某一個特定的測試環境下才可用。該註解可以指定name
與value
鍵值對,當指定的鍵值對與配置好的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。如果不使用該註解,則預設情況下使用的ProfileValueSource
是SystemProfileValueSource
。
@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
類,以及TestContext
、TestExecutionListener
和SmartContextLoader
介面組成。每一個test類會建立一個TestContextManager
,而TestContextManager
管理著一個TestContext
,TestContext
儲存了當前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的mockDirtiesContextBeforeModesTestExecutionListener
: 處理@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...
}
如果locations
和value
屬性都省略,那麼框架會使用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來配置ApplicationContext
,initializers
屬性的值是ApplicationContextInitializer
的實現類的陣列。
@RunWith(SpringRunner.class)
@ContextConfiguration(
classes = TestConfig.class,
initializers = TestAppCtxInitializer.class)
public class MyTest {
// class body...
}
Context Configuration Inheritance(繼承上下文配置)
可以為@ContextConfiguration
註解的inheritLocations
和inheritInitializers
屬性指定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
註解的inheritLocations
和inheritProperties
屬性設定boolean值,可以指定是否繼承父類的property資原始檔或者內聯的property,預設值是true。
Loading a WebApplicationContext(載入WebApplicationContext)
@WebAppConfiguration
註解可以用來載入WebApplicationContext,這個在前面的章節講過一些基礎用法,這裡講解一寫它的額外功能。
ServletTestExecutionListener
為web測試提供了統一的支援,它在每一個測試方法開始之前,通過RequestContextHolder
設定了預設的thread local的狀態,並且基於@WebAppConfiguration
配置的資源建立了MockHttpServletRequest
、MockHttpServletResponse
和ServletWebRequest
,而且保證了MockHttpServletResponse
和ServletWebRequest
會被注入到test例項中,在test執行完成後再將其thread local的狀態清除。
下面的例子展示了哪些mock可以被自動裝配到test例項中,其中WebApplicationContext
和MockServletContext
在整個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
(fromContextCustomizerFactory
)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.maxSize
JVM系統變數的值來改變快取大小。也可以通過程式設計式的使用SpringProperties
API去設定同樣的屬性的值。
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
裡還必須配置一個PlatformTransactionManager
bean,並且需要在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
裡定義一個PlatformTransactionManager
bean,如果存在多個PlatformTransactionManager
bean,那麼可以使用@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指令碼的。
類似的,AbstractTransactionalJUnit4SpringContextTests
和AbstractTransactionalTestNGSpringContextTests
在內部也是使用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方法執行完成之後再執行。下面的例子中,ISOLATED
和AFTER_TEST_METHOD
來自Sql.TransactionMode
和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
)
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
註解的屬性dataSource
和transactionManager
設定值來指定資料來源和事務管理器。
@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等功能。
AbstractTransactionalJUnit4SpringContextTests
在AbstractJUnit4SpringContextTests
的基礎上添加了一些訪問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
待。