領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多正如 Jürgen Höller 在他宣佈 Spring 3.1 M2 釋出的帖子中提到的,Spring TestContext Framework(*) 已經過全面改進,為 @Configuration 類和環境配置檔案提供了卓越的測試支援。
在這篇文章中,我將首先透過一些示例向您介紹這些新的測試功能。然後,我將介紹 TestContext 框架中使這些新功能成為可能的一些新擴充套件點。
請注意:這是我公司部落格 www.swiftmind.com 的交叉釋出。
其核心是,TestContext 框架允許您使用 @ContextConfiguration 註解測試類,以指定用於為測試載入 ApplicationContext 的配置檔案。預設情況下,ApplicationContext 是使用 GenericXmlContextLoader 載入的,它從 XML Spring 配置檔案中載入上下文。然後,您可以透過使用 @Autowired、@Resource 或 @Inject 註解測試類中的欄位來訪問 ApplicationContext 中的 bean。
Spring 3.0 引入了透過 @Configuration 類進行基於 Java 配置的支援,但 TestContext 框架此前並未提供合適的 ContextLoader 來支援測試中的 @Configuration 類。Spring 3.1 M2 為此引入了一個新的 AnnotationConfigContextLoader,並且 @ContextConfiguration 註解已更新,以透過新的 classes 屬性支援 @Configuration 類的宣告。
現在讓我們看一些例子。
Spring 參考手冊的測試章節提供了大量關於如何使用 XML 配置檔案配置整合測試的示例,但我們在此處包含一個示例作為快速介紹。
如果您已經熟悉 Spring TestContext 框架,請隨意跳到下一節。
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
<!-- this bean will be injected into the OrderServiceTest class -->
<bean id="orderService" class="com.example.OrderServiceImpl">
<!-- set properties, etc. -->
</bean>
<!-- other beans -->
</beans>
package com.example;
@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext will be loaded from "classpath:/com/example/OrderServiceTest-context.xml"
@ContextConfiguration
public class OrderServiceTest {
@Autowired
private OrderService orderService;
@Test
public void testOrderService() {
// test the orderService
}
}
在前面的示例中,我們將 JUnit 配置為使用 SpringJUnit4ClassRunner 來執行我們的測試。我們使用 JUnit 的 @RunWith 註解來完成此操作。我們還使用 Spring 的 @ContextConfiguration 註解來註解我們的測試類,而不指定任何屬性。在這種情況下,將使用預設的 GenericXmlContextLoader,並且遵循約定優於配置的原則,Spring 將從 classpath:/com/example/OrderServiceTest-context.xml 載入我們的 ApplicationContext。在 testOrderService() 方法中,我們可以直接測試使用 @Autowired 注入到測試例項中的 OrderService。請注意,orderService 在 OrderServiceTest-context.xml 中被定義為一個 bean。
Spring 3.1 M2 對使用 @Configuration 類進行整合測試的支援與上面的基於 XML 的示例類似。因此,讓我們修改該示例以使用 @Configuration 類和新的 AnnotationConfigContextLoader。
package com.example;
@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext will be loaded from the static inner ContextConfiguration class
@ContextConfiguration(loader=AnnotationConfigContextLoader.class)
public class OrderServiceTest {
@Configuration
static class ContextConfiguration {
// this bean will be injected into the OrderServiceTest class
@Bean
public OrderService orderService() {
OrderService orderService = new OrderServiceImpl();
// set properties, etc.
return orderService;
}
}
@Autowired
private OrderService orderService;
@Test
public void testOrderService() {
// test the orderService
}
}
此示例與基於 XML 的示例之間有一些顯著差異
ContextConfiguration 類中使用 @Configuration 和 @Bean。AnnotationConfigContextLoader 已透過 @ContextConfiguration 的 loader 屬性指定。此外,測試的配置和實現保持不變。
那麼,Spring 如何知道使用靜態內部 ContextConfiguration 類來載入 ApplicationContext 呢?答案是約定優於配置。預設情況下,如果未顯式宣告任何類,AnnotationConfigContextLoader 將在測試類中查詢名為 ContextConfiguration 的靜態內部類。根據 @Configuration 類的要求,此靜態內部類必須是非 final 和非 private 的。
注意:截至 Spring 3.1 M2,預設配置類必須精確命名為 ContextConfiguration。然而,截至 Spring 3.1 RC1,命名限制已取消。換句話說,從 RC1 開始,您可以隨意命名您的預設配置類,但其他要求仍然適用。
在以下示例中,我們將看到如何宣告顯式配置類。
package com.example;
@Configuration
public class OrderServiceConfig {
// this bean will be injected into the OrderServiceTest class
@Bean
public OrderService orderService() {
OrderService orderService = new OrderServiceImpl();
// set properties, etc.
return orderService;
}
}
package com.example;
@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext will be loaded from the OrderServiceConfig class
@ContextConfiguration(classes=OrderServiceConfig.class, loader=AnnotationConfigContextLoader.class)
public class OrderServiceTest {
@Autowired
private OrderService orderService;
@Test
public void testOrderService() {
// test the orderService
}
}
現在我們將靜態內部 ContextConfiguration 類提取到一個名為 OrderServiceConfig 的頂層類中。為了指示 AnnotationConfigContextLoader 使用此配置類而不是依賴預設值,我們只需透過 @ContextConfiguration 的新 classes 屬性宣告 OrderServiceConfig.class。與 @ContextConfiguration 的資源位置 locations 屬性一樣,我們可以透過向 classes 屬性提供 Class[] 陣列來宣告多個配置類——例如:@ContextConfiguration(classes={Config1.class, Config2.class}, ... )。
本文對使用 @Configuration 類進行整合測試的介紹到此結束。現在讓我們來看看 Spring 對環境配置檔案的測試支援。
正如 Chris Beams 在他釋出的 Spring 3.1 M1 公告和後續部落格 Introducing @Profile 中所討論的,Spring 3.1 在框架中引入了對環境和配置檔案(也稱為bean 定義配置檔案)概念的一流支援。截至 Spring 3.1 M2,整合測試也可以配置為針對各種測試場景啟用特定的 bean 定義配置檔案。這是透過使用新的 @ActiveProfiles 註解測試類並提供應在為測試載入 ApplicationContext 時啟用的配置檔案列表來實現的。
注意:@ActiveProfiles 可以與新的 SmartContextLoader SPI 的任何實現一起使用(參見後面的討論),但 @ActiveProfiles **不**支援更簡單的 ContextLoader SPI 的實現。
讓我們來看一些使用 XML 配置和 @Configuration 類的示例。
<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>
package com.bank.service;
@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
public class TransferServiceTest {
@Autowired
private TransferService transferService;
@Test
public void testTransferService() {
// test the transferService
}
}
當執行 TransferServiceTest 時,其 ApplicationContext 將從 classpath 根目錄下的 app-config.xml 配置檔案中載入。如果您檢查 app-config.xml,您會注意到 accountRepository bean 依賴於 dataSource bean;但是,dataSource 未定義為頂層 bean。相反,dataSource 定義了兩次:一次在生產配置檔案中,一次在開發配置檔案中。
透過使用 @ActiveProfiles("dev") 註解 TransferServiceTest,我們指示 Spring TestContext 框架載入 ApplicationContext,並將活動配置檔案設定為 {"dev"}。結果,將建立一個嵌入式資料庫,並且 accountRepository bean 將連線到開發 DataSource 的引用。這很可能就是我們在整合測試中想要的!
以下程式碼列表演示瞭如何實現相同的配置和整合測試,但使用 @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
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
@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();
}
}
package com.bank.service;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=AnnotationConfigContextLoader.class,
classes={TransferServiceConfig.class, StandaloneDataConfig.class, JndiDataConfig.class})
@ActiveProfiles("dev")
public class TransferServiceTest {
@Autowired
private TransferService transferService;
@Test
public void testTransferService() {
// test the transferService
}
}
在此變體中,我們將 XML 配置拆分為三個獨立的 @Configuration 類
TransferServiceConfig:透過使用 @Autowired 進行依賴注入來獲取 dataSourceStandaloneDataConfig:為適用於開發人員測試的嵌入式資料庫定義 dataSourceJndiDataConfig:定義在生產環境中從 JNDI 檢索的 dataSource與基於 XML 的配置示例一樣,我們仍然使用 @ActiveProfiles("dev") 註解 TransferServiceTest,但這次我們透過 @ContextConfiguration 註解指定 AnnotationConfigContextLoader 和所有三個配置類。測試類本身的主體完全保持不變。
有關如何簡化上述 @Configuration 類的詳細資訊,請查閱 Spring 3.1 M1: Introducing @Profile 部落格文章。
自 Spring 2.5 以來,Spring TestContext 框架已根據為給定測試的所有合併上下文資源位置生成的鍵快取 ApplicationContexts 進行整合測試。由於 ContextLoader SPI 僅支援位置,因此此鍵生成演算法足以唯一標識用於載入 ApplicationContext 的配置。然而,隨著對配置類和配置檔案的支援增加,舊演算法已不再適用。
因此,上下文快取鍵生成演算法在 Spring 3.1 M2 中已更新,以包含以下所有內容
@ContextConfiguration)@ContextConfiguration)@ContextConfiguration)@ActiveProfiles)這對您作為開發人員意味著,您可以實現一個宣告特定資源位置或配置類的基本測試類。然後,如果您想針對該基本配置執行測試,但使用不同的活動配置檔案,您可以擴充套件該基本測試類,並使用 @ActiveProfiles 註解每個具體子類,為每個子類提供一組不同的要啟用的配置檔案。因此,這些子類中的每一個都將定義一組唯一的配置屬性,這將導致載入和快取不同的 ApplicationContexts。
正如本文前面所暗示的,Spring 3.1 M2 引入了一個新的 SmartContextLoader SPI,它取代了現有的 ContextLoader SPI。如果您計劃開發或已經開發了自己的自定義 ContextLoader,您可能需要仔細檢視新的 SmartContextLoader 介面。與舊的 ContextLoader 介面不同,SmartContextLoader 可以處理資源位置和配置類。此外,SmartContextLoader 可以在其載入的上下文中設定活動 bean 定義配置檔案。
ContextLoader 將繼續受支援,並且該 SPI 的任何現有實現都應繼續“按原樣”工作;但是,如果您的自定義載入器要支援配置類或環境配置檔案,則需要實現 SmartContextLoader。
如果您一直密切關注到目前為止的示例,您可能已經注意到,在使用配置類時,我們總是必須為 @ContextConfiguration 的 loader 屬性顯式宣告 AnnotationConfigContextLoader.class。但是當我們指定 XML 配置檔案(或依賴約定優於配置)時,預設使用的是 GenericXmlContextLoader。
如果 Spring 能夠注意到我們使用的是配置類還是 XML 資源位置,然後自動選擇正確的 ContextLoader 來載入我們的應用程式上下文,那不是很好嗎?
是的,我們也這麼認為! ;)
因此,對於 Spring 3.1 RC1,我們計劃引入一個 DelegatingSmartContextLoader,它將委託給一系列候選的 SmartContextLoaders(即 GenericXmlContextLoader 和 AnnotationConfigContextLoader),以確定哪個上下文載入器適用於給定測試類的配置。然後將使用獲勝的候選者來實際載入上下文。
一旦這項工作完成,DelegatingSmartContextLoader 將取代 GenericXmlContextLoader 成為預設載入器。請隨時在 JIRA 中關注此開發進度:SPR-8387。
Spring 3.1 為 @Configuration 類和環境配置檔案提供了卓越的測試支援,我們鼓勵您儘快嘗試這些功能。M2 是 3.1 釋出系列的最後一個里程碑。因此,如果您發現任何錯誤或有任何改進建議,現在就是採取行動的時候了!
(*) 參考手冊尚未更新以反映對 @Configuration 類和環境配置檔案的測試支援,但這些功能肯定會在 Spring 3.1 RC1 或 GA 中得到充分文件化。同時,每個新類和註解的 JavaDoc 可以作為很好的起點。