搶先一步
VMware 提供訓練和認證,以加速您的進展。
瞭解更多如同 Jürgen Höller 在他發布 Spring 3.1 M2 發行 文章中提到的,Spring TestContext Framework(*) 已全面改版,為 @Configuration
類別和環境剖面提供一流的測試支援。
在這篇文章中,我將首先帶您瀏覽一些範例,示範這些新的測試功能。然後,我將介紹 TestContext Framework 中一些新的擴充點,這些擴充點使這些新功能成為可能。
請注意:這是從我的公司部落格 www.swiftmind.com 交叉發布的文章。
在其核心,TestContext Framework 允許您使用 @ContextConfiguration
註解測試類別,以指定要使用哪些組態檔來載入測試的 ApplicationContext
。預設情況下,ApplicationContext
是使用 GenericXmlContextLoader
載入的,它從 XML Spring 組態檔載入 Context。然後,您可以通過使用 @Autowired
、@Resource
或 @Inject
註解測試類別中的欄位,從 ApplicationContext
存取 Bean。
Spring 3.0 透過 @Configuration
類別引入了對基於 Java 的組態的支援,但 TestContext Framework 直到現在才提供適當的 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。@ContextConfiguration
的 loader
屬性指定了 AnnotationConfigContextLoader
。否則,測試的組態和實作保持不變。
那麼,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
屬性一樣,我們可以透過將 Class[] 陣列提供給 classes
屬性來宣告多個組態類別 — 例如:@ContextConfiguration(classes={Config1.class, Config2.class}, ... )
。
這結束了使用 @Configuration
類別進行整合測試的介紹。現在讓我們看看 Spring 對環境剖面的測試支援。
正如 Chris Beams 在他的 Spring 3.1 M1 發行 公告和他的後續部落格 Spring 3.1 M1:介紹 @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
定義了兩次:一次在 production 剖面中,一次在 dev 剖面中。
透過使用 @ActiveProfiles("dev")
註解 TransferServiceTest
,我們指示 Spring TestContext Framework 載入 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
的依賴注入來取得 dataSource
StandaloneDataConfig
:為適用於開發人員測試的嵌入式資料庫定義 dataSource
JndiDataConfig
:定義從生產環境中的 JNDI 檢索的 dataSource
與基於 XML 的組態範例一樣,我們仍然使用 @ActiveProfiles("dev")
註解 TransferServiceTest
,但這次我們透過 @ContextConfiguration
註解指定 AnnotationConfigContextLoader
和所有三個組態類別。測試類別本身的主體保持完全不變。
有關如何簡化上述 @Configuration
類別的詳細資訊,請參閱 Spring 3.1 M1:介紹 @Profile 部落格文章。
自 Spring 2.5 以來,Spring TestContext Framework 已根據從給定測試的所有合併 Context 資源位置產生的金鑰,快取整合測試的 ApplicationContext
。由於 ContextLoader
SPI 僅支援位置,因此此金鑰產生演算法足以唯一識別用於載入 ApplicationContext
的組態。但是,隨著對組態類別和剖面的新增支援,舊的演算法已不再足夠。
因此,Context 快取金鑰產生演算法已在 Spring 3.1 M2 中更新,以包含以下所有內容
@ContextConfiguration
)@ContextConfiguration
)@ContextConfiguration
)@ActiveProfiles
)作為開發人員,這對您意味著您可以實作一個基礎測試類別,該類別宣告一組特定的資源位置或組態類別。然後,如果您想針對該基礎組態執行測試,但使用不同的活動剖面,您可以擴充該基礎測試類別,並使用 @ActiveProfiles
註解每個具體的子類別,為每個子類別提供一組不同的活動剖面。因此,這些子類別中的每一個都將定義一組唯一的組態屬性,這些屬性將導致載入和快取不同的 ApplicationContext
。
正如本文前面暗示的那樣,Spring 3.1 M2 引入了新的 SmartContextLoader
SPI,它取代了現有的 ContextLoader
SPI。如果您計劃開發或已經開發了自己的自訂 ContextLoader
,您可能需要仔細查看新的 SmartContextLoader
介面。與舊的 ContextLoader
介面相比,SmartContextLoader
可以處理資源位置和組態類別。此外,SmartContextLoader
可以在它載入的 Context 中設定活動 Bean 定義剖面。
ContextLoader
將繼續受到支援,並且該 SPI 的任何現有實作都應繼續按原樣工作;但是,如果您想在自訂載入器中支援組態類別或環境剖面,則需要實作 SmartContextLoader
。
如果您一直密切關注到目前為止呈現的範例,您可能已經注意到,當使用組態類別時,我們總是必須為 @ContextConfiguration
的 loader
屬性明確宣告 AnnotationConfigContextLoader.class
。但是,當我們指定 XML 組態檔(或依賴慣例優於配置)時,預設情況下會使用 GenericXmlContextLoader
。
如果 Spring 可以注意到我們是否正在使用組態類別或 XML 資源位置,然後自動選擇正確的 ContextLoader
來載入我們的應用程式 Context,這不是很好嗎?
是啊,我們也這麼認為!;)
因此,對於 Spring 3.1 RC1,我們計劃引入 DelegatingSmartContextLoader
,它將委派給候選 SmartContextLoader
的清單(即 GenericXmlContextLoader
和 AnnotationConfigContextLoader
),以確定哪個 Context 載入器適合給定測試類別的組態。然後,將使用獲勝的候選者來實際載入 Context。
一旦完成這項工作,DelegatingSmartContextLoader
將取代 GenericXmlContextLoader
作為預設載入器。歡迎隨時關注 JIRA 中此開發的進度:SPR-8387。
Spring 3.1 為 @Configuration
類別和環境剖面提供了一流的測試支援,我們鼓勵您盡快試用這些功能。M2 是 3.1 發行列車中的最後一個里程碑。因此,如果您發現任何錯誤或對改進有任何建議,現在是採取行動的時候了!
(*) 參考手冊尚未更新以反映對 @Configuration
類別和環境剖面的測試支援,但這些功能肯定會在 Spring 3.1 RC1 或 GA 中得到充分記錄。同時,每個新類別和註解的 JavaDoc 可以作為一個好的起點。