Context Configuration with Environment Profiles
Spring 框架对环境和模式(又名 “bean 定义模式”)的概念提供了一流的支持,可以将集成测试配置为针对不同的测试场景激活特定的 bean 定义模式。这可以通过用 @ActiveProfiles
注释测试类并提供应在加载测试的 ApplicationContext
时激活的模式列表来实现。
您可以将 |
考虑两个带有 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
从类路径根目录的 app-config.xml
配置文件中加载。如果你检查 app-config.xml
,你就会看到 accountRepository
bean 对一个 dataSource
bean 有依赖。然而,dataSource
没有定义为顶级 bean。相反,dataSource
定义了三次:在 production
模式中,在 dev
模式中和在 default
模式中。
通过用 @ActiveProfiles("dev")
为 TransferServiceTest
加注解,我们指示 SpringTestContext Framework
使用 {"dev"}
设置激活的配置文件,加载 ApplicationContext
。这样做创建了一个嵌入式数据库,里面填充了测试数据,并且 accountRepository
bean 用对开发 DataSource
的引用连接起来。这很可能是我们在集成测试中想要的结果。
有时为 default
配置文件分配 bean 很有效。只有在没有特别激活其他配置文件时,才会包含 default
配置文件中的 bean。你可以用它来定义要应用于应用程序默认状态的“fallback
”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
:
从 Spring Framework 5.3 开始,还可以从封闭类继承测试配置。请参阅 |
-
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
}
此外,有时需要以编程方式而不是声明性地解析用于测试的激活配置文件,例如,根据:
-
The current operating system.
-
测试是在持续集成构建服务器上运行。
-
存在某些环境变量。
-
存在自定义类级注释。
-
Other concerns.
要以编程方式解析活动 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)
}
}