搶先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多正如 Jürgen Höller 在宣佈 Spring 3.1 M2 釋出的帖子中提到的那樣,Spring TestContext Framework(*) 已經過全面改進,為 @Configuration
類和環境 Profile 提供了第一等的測試支援。
在本文中,我將首先向您介紹一些示例,演示這些新的測試特性。然後,我將介紹 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 Framework,請隨意跳到下一節。
<?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
從 XML 轉換為 Java。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 對環境 profile 的測試支援。
正如 Chris Beams 在 Spring 3.1 M1 的釋出公告及其後續部落格 Introducing @Profile 中討論的那樣,Spring 3.1 在框架中引入了對環境和 Profile(又稱 bean 定義 Profile)概念的一流支援。從 Spring 3.1 M2 開始,還可以配置整合測試,以針對各種測試場景啟用特定的 bean 定義 Profile。這可以透過使用新的 @ActiveProfiles
註解來註解測試類,並提供在為測試載入 ApplicationContext
時應啟用的 Profile 列表來實現。
注意:@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
將從類路徑根目錄下的 app-config.xml
配置檔案載入。如果您檢查 app-config.xml
,您會注意到 accountRepository
bean 依賴於一個 dataSource
bean;然而,dataSource
沒有被定義為一個頂級 bean。相反,dataSource
定義了兩次:一次在 production Profile 中,一次在 dev Profile 中。
透過使用 @ActiveProfiles("dev")
註解 TransferServiceTest
,我們指示 Spring TestContext Framework 載入 ApplicationContext
,並將其活動 Profile 設定為 {"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
透過依賴注入獲取 dataSource
StandaloneDataConfig
:定義一個適用於開發者測試的嵌入式資料庫的 dataSource
JndiDataConfig
:定義一個在生產環境中從 JNDI 獲取的 dataSource
與基於 XML 的配置示例一樣,我們仍然使用 @ActiveProfiles("dev")
註解 TransferServiceTest
,但這次我們透過 @ContextConfiguration
註解指定了 AnnotationConfigContextLoader
和所有三個配置類。測試類本身的主體完全保持不變。
有關如何簡化上述 @Configuration
類的詳細資訊,請參閱Spring 3.1 M1: Introducing @Profile 部落格文章。
自 Spring 2.5 以來,Spring TestContext Framework 根據為給定測試合併的所有上下文資源位置生成的 key 來快取整合測試的 ApplicationContexts
。由於 ContextLoader
SPI 只支援 locations,這個 key 生成演算法足以唯一標識用於載入 ApplicationContext
的配置。然而,隨著對配置類和 Profile 的支援增加,舊的演算法已不再適用。
因此,Spring 3.1 M2 中更新了上下文快取 key 生成演算法,以包含以下所有內容:
@ContextConfiguration
)@ContextConfiguration
)@ContextConfiguration
)@ActiveProfiles
)作為開發者,這意味著您可以實現一個基礎測試類,該類宣告一組特定的資源位置或配置類。然後,如果您想針對該基礎配置執行測試,但使用不同的活動 Profile,您可以擴充套件該基礎測試類,並使用 @ActiveProfiles
註解每個具體的子類,為每個子類提供一組不同的 Profile 來啟用。因此,這些子類中的每一個都將定義一組唯一的配置屬性,這將導致載入和快取不同的 ApplicationContexts
。
正如本文前面提到的,Spring 3.1 M2 引入了一個新的 SmartContextLoader
SPI,它取代了現有的 ContextLoader
SPI。如果您計劃開發或已經開發了自己的自定義 ContextLoader
,您可能需要仔細研究新的 SmartContextLoader
介面。與舊的 ContextLoader
介面相比,SmartContextLoader
可以處理資源位置和配置類。此外,SmartContextLoader
可以在其載入的上下文中設定活動的 bean 定義 Profile。
ContextLoader
將繼續得到支援,並且該 SPI 的任何現有實現都應繼續按原樣工作;但是,如果您想在自定義載入器中支援配置類或環境 Profile,您將需要實現 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
類和環境 Profile 提供了第一等的測試支援,我們鼓勵您儘快試用這些功能。M2 是 3.1 釋出列車中的最後一個里程碑。因此,如果您發現任何錯誤或有任何改進建議,現在就是採取行動的時候了!
(*) 參考手冊尚未更新以反映對 @Configuration
類和環境 Profile 的測試支援,但這些功能肯定會在 Spring 3.1 RC1 或 GA 版本釋出時得到完善的文件。在此期間,新類和註解的 JavaDoc 可以作為一個很好的起點。