使用环境配置文件

Spring框架对环境和配置文件(也称为“bean定义配置文件”)的概念提供了一流的支持,集成测试可以配置激活特定的bean定义配置文件以适应各种测试场景。通过在测试类上注释@ActiveProfiles注解并提供应该在加载测试的ApplicationContext时激活的配置文件列表来实现这一点。

您可以使用@ActiveProfilesSmartContextLoader SPI的任何实现,但不支持与旧的ContextLoader SPI的实现一起使用@ActiveProfiles

考虑两个示例,一个使用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将从“classpath:/app-config.xml”加载
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {

	@Autowired
	TransferService transferService;

	@Test
	void testTransferService() {
		// 测试transferService
	}
}
@ExtendWith(SpringExtension::class)
// ApplicationContext将从“classpath:/app-config.xml”加载
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {

	@Autowired
	lateinit var transferService: TransferService

	@Test
	fun testTransferService() {
		// 测试transferService
	}
}

当运行TransferServiceTest时,它的ApplicationContext将从类路径根目录中的app-config.xml配置文件加载。如果检查app-config.xml,您会发现accountRepository bean 依赖于dataSource bean。但是,dataSource并未作为顶级bean定义。相反,dataSource被定义了三次:在dev配置文件中,在production配置文件中以及在default配置文件中。

通过使用@ActiveProfiles("dev")注解TransferServiceTest,我们指示Spring TestContext Framework使用激活的配置文件设置为{"dev"}加载ApplicationContext。结果,将创建一个嵌入式数据库并用测试数据填充,accountRepository bean将与开发DataSource的引用连接。这很可能是我们在集成测试中想要的。

有时将bean分配给default配置文件是有用的。默认配置文件中的bean仅在没有特定激活的配置文件时包含。您可以使用此功能定义“回退”bean,以在应用程序的默认状态下使用。例如,您可以明确为devproduction配置文件提供数据源,但在这两者都未激活时定义一个内存数据源作为默认值。

以下代码清单演示了如何使用@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() {
		// 测试 transferService
	}
}
@SpringJUnitConfig(
		TransferServiceConfig::class,
		StandaloneDataConfig::class,
		JndiDataConfig::class,
		DefaultDataConfig::class)
@ActiveProfiles("dev")
class TransferServiceTest {

	@Autowired
	lateinit var transferService: TransferService

	@Test
	fun testTransferService() {
		// 测试 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 开始,测试配置也可以从封闭类中继承。有关详细信息,请参阅 @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" 配置文件
class TransferServiceTest extends AbstractIntegrationTest {

	@Autowired
	TransferService transferService;

	@Test
	void testTransferService() {
		// 测试 transferService
	}
}
// 从超类继承 "dev" 配置文件
class TransferServiceTest : AbstractIntegrationTest() {

	@Autowired
	lateinit var transferService: TransferService

	@Test
	fun testTransferService() {
		// 测试 transferService
	}
}

@ActiveProfiles 还支持一个 inheritProfiles 属性,可用于禁用活动配置文件的继承,如下例所示:

  • Java

  • Kotlin

// 通过自定义解析器以编程方式覆盖 "dev" 配置文件
@ActiveProfiles(profiles = "production", inheritProfiles = false)
class ProductionTransferServiceTest extends AbstractIntegrationTest {
	// 测试内容
}
// 通过自定义解析器以编程方式覆盖 "dev" 配置文件
@ActiveProfiles("production", inheritProfiles = false)
class ProductionTransferServiceTest : AbstractIntegrationTest() {
	// 测试内容
}

此外,有时需要以编程方式解析测试的活动配置文件,而不是声明式地 — 例如,基于以下条件:

  • 当前操作系统。

  • 测试是否在持续集成构建服务器上运行。

  • 某些环境变量的存在。

  • 自定义类级别注解的存在。

  • 其他相关问题。

为了以编程方式解析活动 bean 定义配置文件,您可以实现一个自定义的 ActiveProfilesResolver 并通过 @ActiveProfilesresolver 属性进行注册。有关更多信息,请参阅相应的 javadoc。以下示例演示了如何实现和注册一个自定义的 OperatingSystemActiveProfilesResolver

  • Java

  • Kotlin

// 通过自定义解析器以编程方式覆盖 "dev" 配置文件
@ActiveProfiles(
		resolver = OperatingSystemActiveProfilesResolver.class,
		inheritProfiles = false)
class TransferServiceTest extends AbstractIntegrationTest {
	// 测试内容
}
// 通过自定义解析器以编程方式覆盖 "dev" 配置文件
@ActiveProfiles(
		resolver = OperatingSystemActiveProfilesResolver::class,
		inheritProfiles = false)
class TransferServiceTest : AbstractIntegrationTest() {
	// 测试内容
}
  • Java

  • Kotlin

public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver {

	@Override
	public String[] resolve(Class<?> testClass) {
		String profile = ...;
		// 根据操作系统确定 profile 的值
		return new String[] {profile};
	}
}
class OperatingSystemActiveProfilesResolver : ActiveProfilesResolver {

	override fun resolve(testClass: Class<*>): Array<String> {
		val profile: String = ...
		// 根据操作系统确定 profile 的值
		return arrayOf(profile)
	}
}