使用環境配置檔案進行上下文配置
Spring Framework 對環境和配置檔案(又稱“bean 定義配置檔案”)的概念提供了頭等支援,並且可以配置整合測試以針對各種測試場景啟用特定的 bean 定義配置檔案。這透過使用 @ActiveProfiles 註解標記測試類並提供一個應在載入測試的 ApplicationContext 時啟用的配置檔案列表來實現。
您可以將 @ActiveProfiles 與 SmartContextLoader SPI 的任何實現一起使用,但 @ActiveProfiles 不支援與舊版 ContextLoader SPI 的實現一起使用。 |
考慮兩個使用 XML 配置和 @Configuration 類的示例
<!-- app-config.xml -->
<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 profile="default">
<jdbc:embedded-database id="dataSource">
<jdbc:script
location="classpath:com/bank/config/sql/schema.sql"/>
</jdbc:embedded-database>
</beans>
</beans>
-
Java
-
Kotlin
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
TransferService transferService;
@Test
void testTransferService() {
// test the transferService
}
}
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
lateinit var transferService: TransferService
@Test
fun testTransferService() {
// test the transferService
}
}
當 TransferServiceTest 執行時,其 ApplicationContext 從 classpath 根目錄中的 app-config.xml 配置檔案載入。如果您檢查 app-config.xml,您可以看到 accountRepository bean 依賴於 dataSource bean。但是,dataSource 沒有被定義為頂級 bean。相反,dataSource 被定義了三次:在 production 配置檔案中,在 dev 配置檔案中,以及在 default 配置檔案中。
透過使用 @ActiveProfiles("dev") 註解 TransferServiceTest,我們指示 Spring TestContext Framework 載入 ApplicationContext,並將活動配置檔案設定為 {"dev"}。結果是,將建立一個嵌入式資料庫並填充測試資料,並且 accountRepository bean 被連線到開發 DataSource 的引用。這可能就是我們在整合測試中想要的。
有時將 bean 分配給 default 配置檔案很有用。預設配置檔案中的 bean 僅在沒有其他配置檔案被明確啟用時才會被包含。您可以使用此功能定義“回退”bean,以便在應用程式的預設狀態下使用。例如,您可以明確為 dev 和 production 配置檔案提供資料來源,但在這些配置檔案均未啟用時,將記憶體資料來源定義為預設資料來源。
以下程式碼列表演示瞭如何使用 @Configuration 類而不是 XML 來實現相同的配置和整合測試
-
Java
-
Kotlin
@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("dev")
class StandaloneDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build()
}
}
-
Java
-
Kotlin
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
@Configuration
@Profile("production")
class JndiDataConfig {
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
val ctx = InitialContext()
return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}
}
-
Java
-
Kotlin
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
@Configuration
@Profile("default")
class DefaultDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build()
}
}
-
Java
-
Kotlin
@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();
}
}
@Configuration
class TransferServiceConfig {
@Autowired
lateinit var dataSource: DataSource
@Bean
fun transferService(): TransferService {
return DefaultTransferService(accountRepository(), feePolicy())
}
@Bean
fun accountRepository(): AccountRepository {
return JdbcAccountRepository(dataSource)
}
@Bean
fun feePolicy(): FeePolicy {
return ZeroFeePolicy()
}
}
-
Java
-
Kotlin
@SpringJUnitConfig({
TransferServiceConfig.class,
StandaloneDataConfig.class,
JndiDataConfig.class,
DefaultDataConfig.class})
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
TransferService transferService;
@Test
void testTransferService() {
// test the transferService
}
}
@SpringJUnitConfig(
TransferServiceConfig::class,
StandaloneDataConfig::class,
JndiDataConfig::class,
DefaultDataConfig::class)
@ActiveProfiles("dev")
class TransferServiceTest {
@Autowired
lateinit var transferService: TransferService
@Test
fun testTransferService() {
// test the transferService
}
}
在此變體中,我們將 XML 配置拆分為四個獨立的 @Configuration 類
-
TransferServiceConfig:透過使用@Autowired進行依賴注入來獲取dataSource。 -
StandaloneDataConfig:為適合開發人員測試的嵌入式資料庫定義一個dataSource。 -
JndiDataConfig:定義一個在生產環境中從 JNDI 檢索的dataSource。 -
DefaultDataConfig:為預設嵌入式資料庫定義一個dataSource,以防沒有配置檔案處於活動狀態。
與基於 XML 的配置示例一樣,我們仍然使用 @ActiveProfiles("dev") 註解 TransferServiceTest,但這次我們使用 @ContextConfiguration 註解指定所有四個配置類。測試類本身的內容保持完全不變。
通常情況下,在給定專案中,多個測試類會使用一套相同的配置檔案。因此,為了避免重複宣告 @ActiveProfiles 註解,您可以在基類上宣告一次 @ActiveProfiles,子類將自動從基類繼承 @ActiveProfiles 配置。在以下示例中,@ActiveProfiles(以及其他註解)的宣告已移至抽象超類 AbstractIntegrationTest
測試配置也可以從封閉類繼承。詳情請參閱@Nested 測試類配置。 |
-
Java
-
Kotlin
@SpringJUnitConfig({
TransferServiceConfig.class,
StandaloneDataConfig.class,
JndiDataConfig.class,
DefaultDataConfig.class})
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
@SpringJUnitConfig(
TransferServiceConfig::class,
StandaloneDataConfig::class,
JndiDataConfig::class,
DefaultDataConfig::class)
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
-
Java
-
Kotlin
// "dev" profile inherited from superclass
class TransferServiceTest extends AbstractIntegrationTest {
@Autowired
TransferService transferService;
@Test
void testTransferService() {
// test the transferService
}
}
// "dev" profile inherited from superclass
class TransferServiceTest : AbstractIntegrationTest() {
@Autowired
lateinit var transferService: TransferService
@Test
fun testTransferService() {
// test the transferService
}
}
@ActiveProfiles 還支援 inheritProfiles 屬性,該屬性可用於停用活動配置檔案的繼承,如下例所示
-
Java
-
Kotlin
// "dev" profile overridden with "production"
@ActiveProfiles(profiles = "production", inheritProfiles = false)
class ProductionTransferServiceTest extends AbstractIntegrationTest {
// test body
}
// "dev" profile overridden with "production"
@ActiveProfiles("production", inheritProfiles = false)
class ProductionTransferServiceTest : AbstractIntegrationTest() {
// test body
}
此外,有時需要以程式設計方式而不是宣告性地解析測試的活動配置檔案——例如,基於
-
當前作業系統。
-
測試是否在持續整合構建伺服器上執行。
-
某些環境變數的存在。
-
自定義類級註解的存在。
-
其他考慮。
要以程式設計方式解析活動 bean 定義配置檔案,您可以實現自定義的 ActiveProfilesResolver,並使用 @ActiveProfiles 的 resolver 屬性進行註冊。有關更多資訊,請參閱相應的 javadoc。以下示例演示瞭如何實現和註冊自定義的 OperatingSystemActiveProfilesResolver
-
Java
-
Kotlin
// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
resolver = OperatingSystemActiveProfilesResolver.class,
inheritProfiles = false)
class TransferServiceTest extends AbstractIntegrationTest {
// test body
}
// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
resolver = OperatingSystemActiveProfilesResolver::class,
inheritProfiles = false)
class TransferServiceTest : AbstractIntegrationTest() {
// test body
}
-
Java
-
Kotlin
public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver {
@Override
public String[] resolve(Class<?> testClass) {
String profile = ...;
// determine the value of profile based on the operating system
return new String[] {profile};
}
}
class OperatingSystemActiveProfilesResolver : ActiveProfilesResolver {
override fun resolve(testClass: Class<*>): Array<String> {
val profile: String = ...
// determine the value of profile based on the operating system
return arrayOf(profile)
}
}