1. 程式人生 > >spring5-整合測試

spring5-整合測試

11.1 概述

能夠在不需要部署到應用伺服器或連線到其它企業基礎服務的前提下做一些整合測試是很重要的。這將使你能夠測試以下內容:

  • Spring IoC容器上下文的正確裝配。
  • 使用JDBC或其它ORM工具訪問資料。這將包括SQL語句、Hibernate查詢和JPA實體對映的正確性等等這些內容。

Spring Framework在spring-test模組中為整合測試提供了強有力的支援。該Jar包的實際名字可能會包含釋出版本號而且可能是org.springframework.test這樣長的形式,這取決於你是從哪獲得的(請參閱section on Dependency Management

中的解釋)。這個庫包括了org.springframework.test包,其中包含了使用Spring容器進行整合測試的重要的類。這個測試不信賴於應用伺服器和其它的釋出環境。這些測試會比單元測試稍慢但比同類型的Selenium測試或信賴於釋出到應用伺服器的遠端測試要快得多。

在Spring2.5及之後的版本,單元和整合測試支援是以註解驅動Spring TestContext框架這樣的形式提供的。TestContext框架對實際使用的測試框架是不可知的

11.2 整合測試的目標

Spring的整合測試支援有以下幾點主要目標:

下面幾節將解釋每個目標並提供實現和配置詳情的連結。

11.2.1 上下文管理和快取

Spring TestContex框架對Spring ApplicationContext 和WebApplicationContext提供一致性載入並對它們進行快取。對載入的上下文進行快取提供支援是很重要的,因為啟動時間是個問題——不是因為Spring自己的開銷,而是被Spring容器初始化的物件需要時間去初始化。比如,一個擁有50到100個Hibernate對映檔案的專案可能花費10到20秒的時間去載這些對映檔案,在執行每一個測試工具中的測試用例之前都會引發開銷並導致總體測試執行變緩慢,降低開發效率。

測試類通常宣告一批XML的資源路徑或者Groovy的配置元資料——通常在類路徑中——或者是一批用於配置應用程式的註解類。這些路徑或者類跟在web.xml或者其它用於生產部署的配置檔案中指定的是一樣的。

通常,一旦被載入過一次,ApplicationContext就將被用於每個測試中。因此啟動開銷在一次測試集中將只會引發一次,隨後執行的測試將會快得多。在這裡,“測試集”的意思是在同一個JVM的所有測試——比如說,對給定專案或者模組的一次Ant、Maven或者Gradle構建執行的所有測試。在不太可能的情況下,一個測試會破壞應用上下文並引起重新載入——比如,修改一個bean定義或者應用程式物件的狀態——TestContex框架將被設定為在開始下個測試之前重新載入配置並重建應用上下文。

11.2.2 測試配置的信賴注入

當TestContext框架載入你的應用程式上下文的時候,它將通過信賴注入有選擇性地配置測試例項。這為使用你的應用程式上下文中的預配置bean來建立測試配置提供了一個很方便的機制。這裡有一個很大的便處就是你可以在不同的測試場景中重複使用應用程式上下文(比如,配置基於Spring管理的物件圖、事務代理、資料來源等等),這樣省去了為每個測試用例建立複雜的測試配置的必要。

舉例說明,考慮這樣一個場景,我們有一個類叫做HibernateTitleRepository,它實現了Title領域實體的資料訪問邏輯。我們想編寫整合測試來測試以下方面:

  • Spring配置:總的來說,就是與HibernateTitleRepository配置有關的一切是否正確和存在?
  • Hibernate對映檔案:是否所有對映都正確,並且延遲載入的設定是否準備就緒?
  • HibernateTitleRepository的邏輯:此類中的配置例項是否與預期一致?

檢視使用TestContext框架進行測試配置的信賴注入。

11.2.3 事務管理

測試中訪問一個真實資料庫的一個常見的問題是在持久層儲存狀態付出的努力。即使你使用開發環境的資料庫,改變相應的狀態也會影響將來的測試。並且,許多操作——插入或者改變持久層資料——也不能在事務之外執行(或者驗證)。

TestContext框架解決了這個問題。預設行為下,這個框架將為每個測試建立並回滾一個事務。你只需簡單的假定事務是存在的並寫你的程式碼即可。如果你呼叫事務代理物件,他們也會根據它們配置的語義正確執行。而且,如果一個測試方法在執行相應事務時刪除了選定表中的內容,事務預設情況下會進行回滾,資料庫會回到測試執行前的那個狀態。事務通過定義在應用程式上下文中的PlatformTransactionManager bean來得到支援。

如果你需要提交事務——通常不會這樣做,但有時當你想用一個特定的測試來填充或者修改資料庫時也會顯得有用——TestContext框架將根據@Commit註解的指示對事務進行提交而不是回滾。

檢視使用TestContext框架進行事務管理。

11.2.3 整合測試的支援類

Spring TestContext框架提供了一些支援來簡化整合測試的編寫。這些基礎類為測試框架提供了定義良好的鉤子,還有一些便利的例項變數和方法,使你能夠訪問:

  • ApplicationContext,用於從整體上來進行顯示的bean查詢或者測試上下文的狀態。
  • JdbcTemplate,用於執行SQL語句來查詢資料庫。這些的查詢可用於確認執行資料庫相關的應用程式程式碼前後資料庫的狀態,並且Spring保證這些查詢與應用程式程式碼在同一個事務作用域中執行。如果需要與ORM工具協同使用,請確保避免誤報。

還有,你可能想用特定於你的專案的例項和方法來建立你自己自定義的,應用程式範圍的超類。

檢視TestContext框架的支援類。

的,因此可以使用包括 JUnit, TestNG等等許多測試手段。

11.3 JDBC測試支援

org.springframework.test.jdbc是包含JdbcTestUtils的包,它是一個JDBC相關的工具方法集,意在簡化標準資料庫測試場景。特別地,JdbcTestUtils提供以下靜態工具方法:

  • countRowsInTable(..):統計給定表的行數。
  • countRowsInTableWhere(..):使用提供的where語句進行篩選統計給定表的行數。
  • deleteFromTables(..):刪除特定表的全部資料。
  • deleteFromTableWhere(..):使用提供的where語句進行篩選並刪除給定表的資料。
  • dropTables(..):刪除指定的表。

注意AbstractTransactionalJUnit4SpringContextTestsAbstractTransactionalTestNGSpringContextTests提供了委託給前面所述的JdbcTestUtils中的方法的簡便方法。

spring-jdbc模組提供了配置和啟動嵌入式資料庫的支援,可用於與資料庫互動的整合測試中。

詳見Section 15.8, “嵌入式資料庫支援”ection 15.8.5, “使用嵌入式資料庫測試資料訪問邏輯”

11.4 註解

11.4.1 Spring測試註解

Spring框架提供以下Spring特定的註解集合,你可以在單元和整合測試中協同TestContext框架使用它們。請參考相應的JAVA幫助文件作進一步瞭解,包括預設的屬性,屬性別名等等。、

@BootstrapWith

@BootstrapWith是一個用於配置Spring TestContext框架如何引導的類級別的註解。具體地說,@BootstrapWith用於指定一個自定義的TestContextBootstrapper。請檢視引導TestContext框架作進一步瞭解。

@ContextConfiguration

@ContextConfiguration定義了類級別的元資料來決定如何為整合測試來載入和配置應用程式上下文。具體地說,@ContextConfiguration聲明瞭用於載入上下文的應用程式上下文資源路徑和註解類。

資源路徑通常是類路徑中的XML配置檔案或者Groovy指令碼;而註解類通常是使用@Configuration註解的類。但是,資源路徑也可以指向檔案系統中的檔案和指令碼,解決類也可能是元件類等等。

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

作為宣告資源路徑或註解類的替代方案或補充,@ContextConfiguration可以用於宣告ApplicationContextInitializer類。

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

@ContextConfiguration偶爾也被用作宣告ContextLoader策略。但注意,通常你不需要顯示的配置載入器,因為預設的載入器已經支援資源路徑或者註解類以及初始化器。

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

@ContextConfiguration預設對繼承父類定義的資源路徑或者配置類以及上下文初始化器提供支援。

參閱Section 11.5.4, 上下文管理和@ContextConfiguration幫助文件作進一步瞭解。

@WebAppConfiguration

@WebAppConfiguration是一個用於宣告整合測試所載入的ApplicationContext須是WebApplicationContext的類級別的註解。測試類的@WebAppConfiguration註解只是為了保證用於測試的WebApplicationContext會被載入,它使用”file:src/main/webapp”路徑預設值作為web應用的根路徑(即,資源基路徑)。資源基路徑用於幕後建立一個MockServletContext作為測試的WebApplicationContext的ServletContext。

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

注意@WebAppConfiguration必須和@ContextConfiguration一起使用,或者在同一個測試類,或者在測試類層次結構中。請參閱@WebAppConfiguration幫助文件作進一步瞭解。

@ContextHierarchy

@ContextHierarchy是一個用於為整合測試定義ApplicationContext層次結構的類級別的註解。@ContextHierarchy應該宣告一個或多個@ContextConfiguration例項列表,其中每一個定義上下文層次結構的一個層次。下面的例子展示了在同一個測試類中@ContextHierarchy的使用方法。但是,@ContextHierarchy一樣可以用於測試類的層次結構中。

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

如果你想合併或者覆蓋一個測試類的層次結構中的應用程式上下文中指定層次的配置,你就必須在類層次中的每一個相應的層次通過為@ContextConfiguration的name屬性提供與該層次相同的值的方式來顯示地指定這個層次。請參閱上下文層次關係和@ContextHierarchy幫助文件來獲得更多的示例。

@ActiveProfiles

@ActiveProfiles是一個用於當整合測試載入ApplicationContext的時候宣告哪一個bean definition profiles被啟用的類級別的註解。

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

@ActiveProfiles預設為繼承啟用的在超類宣告的 bean definition profiles提供支援。通過實現一個自定義的 ActiveProfilesResolver並通過@ActiveProfiles的resolver屬性來註冊它的程式設計的方式來解決啟用bean definition profiles問題也是可行的。

參閱使用環境profiles來配置上下文和@ActiveProfiles幫助文件作進一步瞭解。

參閱使用環境profiles來配置上下文和@ActiveProfiles幫助文件作進一步瞭解。

@TestPropertySource

@TestPropertySource是一個用於為整合測試載入ApplicationContext時配置屬性檔案的位置和增加到Environment中的PropertySources集中的內聯屬性的類級別的註解。

測試屬性源比那些從系統環境或者Java系統屬性以及通過@PropertySource或者程式設計方式宣告方式增加的屬性源具有更高的優先順序。而且,內聯屬性比從資源路徑載入的屬性具有更高的優先順序。

下面的例子展示瞭如何從類路徑中宣告屬性檔案。

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

面的例子展示瞭如何宣告內聯屬性。

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

@DirtiesContext

@DirtiesContext指明測試執行期間該Spring應用程式上下文已經被弄髒(也就是說通過某種方式被更改或者破壞——比如,更改單例bean的狀態)。當應用程式上下文被標為”髒”,它將從測試框架快取中被移除並關閉。因此,Spring容器將為隨後需要同樣配置元資料的測試而被重建。

@DirtiesContext可以在同一個類或者類層次結構中的類級別和方法級別中使用。在這個場景下,應用程式上下文將在任意此註解的方法之前或之後以及當前測試類之前或之後被標為“髒”,這取決於配置的methodMode和classMode。

下面的例子解釋了在多種配置場景下什麼時候上下文會被標為“髒”。

  • 當在一個類中宣告並將類模式設為BEFORE_CLASS,則在當前測試類之前。
@DirtiesContext(classMode = BEFORE_CLASS)
public class FreshContextTests {
// some tests that require a new Spring container
}
  • 當在一個類中宣告並將類模式設為AFTER_CLASS(也就是,預設的類模式),則在當前測試類之後。
@DirtiesContext
public class ContextDirtyingTests {
// some tests that result in the Spring container being dirtied
}
  • 當在一個類中宣告並將類模式設為BEFORE_EACH_TEST_METHOD,則在當前測試類的每個方法之前。
@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD)
public class FreshContextTests {
// some tests that require a new Spring container
}
  • 當在一個類中宣告並將類模式設為AFTER_EACH_TEST_METHOD,則在當前測試類的每個方法之後。
@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD)
public class ContextDirtyingTests {
// some tests that result in the Spring container being dirtied
}
  • 當在一個方法中宣告並將方法模式設為BEFORE_METHOD,則在當前方法之前。
@DirtiesContext(methodMode = BEFORE_METHOD)
@Test
public void testProcessWhichRequiresFreshAppCtx() {
// some logic that requires a new Spring container
}
  • 當在一個方法中宣告並將方法模式設為AFTER_METHOD(也就是說,預設的方法模式),則在當前方法之後。
@DirtiesContext
@Test
public void testProcessWhichDirtiesAppCtx() {
// some logic that results in the Spring container being dirtied
}

如果@DirtiesContext被用於上下文被配置為通過@ContextHierarchy定義的上下文層次中的一部分的測試中,則hierarchyMode標誌可用於控制如何宣告上下文快取。預設將使用一個窮舉演算法用於清除包括不僅當前層次而且與當前測試擁有共同祖先的其它上下文層次的快取。所有在擁有共同祖先上下文的子層次的應用程式上下文都會從上下文中被移除並關閉。如果窮舉演算法對於特定的使用場景顯得有點威力過猛,那麼你可以指定一個更簡單的當前層演算法來代替,如下所。

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

public class ExtendedTests extends BaseTests {

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

參閱DirtiesContext.HierarchyMode幫助文件以獲得窮舉和當前層演算法更詳細的瞭解。

@TestExecutionListeners

@TestExecutionListeners定義了一個類級別的元資料,用於配置需要用TestContextManager進行註冊的TestExecutionListener實現。通常,@TestExecutionListeners與@ContextConfiguration一起使用。

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

@TestExecutionListeners預設支援繼承監聽器。參閱幫助文件獲得示例和更詳細的瞭解。

@Commit

@Commit指定事務性的測試方法在測試方法執行完成後對事務進行提交。@Commit可以用作@Rollback(false)的直接替代,以更好的傳達程式碼的意圖。和@Rollback一樣,@Commit可以在類層次或者方法層級宣告。

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

@Rollback

@Rollback指明當測試方法執行完畢的時候是否對事務性方法中的事務進行回滾。如果為true,則進行回滾;否則,則提交(請參加@Commit)。在Spring TestContext框架中,整合測試預設的Rollback語義為true,即使你不顯示的指定它。

當被宣告為方法級別的註解,則@Rollback為特定的方法指定回滾語義,並覆蓋類級別的@Rollback和@Commit語義。

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

@BeforeTransaction

@BeforeTransaction指明通過Spring的@Transactional註解配置為需要在事務中執行的測試方法在事務開始之前先執行註解的void方法。從Spring框架4.3版本起,@BeforeTransaction方法不再需要為public並可能被宣告為基於Java8的介面的預設方法。

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

@AfterTransaction

@AfterTransaction指明通過Spring的@Transactional註解配置為需要在事務中執行的測試方法在事務結束之後執行註解的void方法。從Spring框架4.3版本起,@AfterTransaction方法不再需要為public並可能被宣告為基於Java8的介面的預設方法。

@AfterTransaction
void afterTransaction() {
// logic to be executed after a transaction has ended
}

@Sql

@Sql用於註解測試類或者測試方法,以讓在整合測試過程中配置的SQL指令碼能夠在給定的的資料庫中得到執行。

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

請參閱通過@sql宣告執行的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註解的容器註解。@SqlGroup可以直接使用,通過宣告幾個巢狀的@Sql註解,也可以與Java8的可重複註解支援協同使用,即簡單地在同一個類或方法上宣告幾個@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
}

11.4.2 標準註解支援

以下註解為Spring TestContext 框架所有的配置提供標準語義支援。注意這些註解不僅限於測試,可以用在Spring框架的任意地方。

  • @Autowired
  • @Qualifier
  • @Resource(javax.annotation)如果JSR-250存在
  • @ManagedBean(javax.annotation)如果JSR-250存在
  • @Inject(javax.inject)如果JSR-330存在
  • @Named(javax.inject)如果JSR-330存在
  • @PersistenceContext(javax.persistence)如果JPA存在
  • @PersistenceUnit(javax.persistence)如果JPA存在
  • @Required
  • @Transactional

在Spring TestContext 框架中,@PostConstruct 和 @PreDestroy 可以通過標準語義在配置於應用程式上下文的任意應用程式元件中使用; 但是, 這些生命週期註解在實際測試類中只有很有限的作用。如果一個測試類的方法被註解為@PostConstruct,這個方法將在test框架中的任何before方法(也就是被JUnit中的@Before註解方法)呼叫之前被執行, 這個規則將被應用於測試類的每個方法。另一方面,如果一個測試類的方法被註解為 @PreDestroy,這個方法將永遠不會被執行。因為建議在測試類中使用test 框架的測試生命週期回撥來代替使用@PostConstruct and @PreDestroy。

11.4.3 Spring JUnit 4 測試註解

@IfProfileValue指明該測試只在特定的測試環境中被啟用。如果ProfileValueSource配置的name屬性與此註解配置的name屬性一致,這該測試將被啟用。否則,該測試將被禁用並忽略。

@IfProfileValue可以用在類級別、方法級別或者兩個同時。使用類級別的@IfProfileValue註解優先於當前類或其子類的任意方法的使用方法級別的註解。有@IfProfileValue註解意味著則測試被隱式開啟。這與JUnit4的@Ignore註解是相類似的,除了使用@Ignore註解是用於禁用測試的之外。

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

或者,你可以 配置@IfProfileValue使用values列表(或語義)來實現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配置的profile值時指定使用什麼樣的ProfileValueSource型別。如果一個測試沒有指定@ProfileValueSourceConfiguration,那麼預設使用SystemProfileValueSource。

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

@Timed

@Timed用於指明被註解的測試必須在指定的時限(毫秒)內結束。如果測試超過指定時限,就當作測試失敗。

時限包括測試方法本身所耗費的時間,包括任何重複(請檢視@Repeat)及任意初始化和銷燬所用的時間。

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

Spring的@Timed註解與JUnit 4的@Test(timeout=…​)支援相比具有不同的語義。確切地說,由於在JUnit 4中處理方法執行超時的方式(也就是,在獨立純程中執行該測試方法),如果一個測試方法執行時間太長,@Test(timeout=…​)將直接判定該測試失敗。而Spring的@Timed則不直接判定失敗而是等待測試完成。

@Repeat

@Repeat指明該測試方法需被重複執行。註解指定該測試方法被重複的次數。重複的範圍包括該測試方法自身也包括相應的初始化和銷燬方法。

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

11.4.4 Meta-Annotation Support for Testing

可以將大部分測試相關的註解當作meta-annotations使用,以建立自定義組合註解來減少測試集中的重複配置。

下面的每個都可以在TestContext框架中被當作meta-annotations使用。

  • @BootstrapWith
  • @ContextConfiguration
  • @ContextHierarchy
  • @ActiveProfiles
  • @TestPropertySource
  • @DirtiesContext
  • @WebAppConfiguration
  • @TestExecutionListeners
  • @Transactional
  • @BeforeTransaction
  • @AfterTransaction
  • @Commit
  • @Rollback
  • @Sql
  • @SqlConfig
  • @SqlGroup
  • @Repeat
  • @Timed
  • @IfProfileValue
  • @ProfileValueSourceConfiguration

例如,如果發現我們在基於JUnit 4的測試集中重複以下配置…

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

然後我們就可以像下面一樣使用我們自定義的@TransactionalDevTest註解來簡化每個類的配置:

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

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

想獲得詳情,請檢視Spring註解程式設計模型