Loading...
Spring Framework Reference Documentation 7.0.2의 Context Configuration with Environment Profiles의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
Spring Framework는 환경과 프로필(일명 "bean definition 프로필") 개념을 1급으로 지원하며,
integration test는 다양한 testing 시나리오에 대해 특정 bean definition 프로필을 활성화하도록
설정할 수 있습니다. 이는 test 클래스에 @ActiveProfiles 어노테이션을 적용하고
test를 위한 ApplicationContext를 로딩할 때 활성화되어야 하는 프로필 목록을 제공함으로써
달성됩니다.
@ActiveProfiles는 어떤SmartContextLoaderSPI 구현과도 함께 사용할 수 있지만, 이전ContextLoaderSPI 구현과는 함께 지원되지 않습니다.
XML 설정과 @Configuration 클래스를 사용하는 두 가지 예를 생각해 보겠습니다:
1<!-- app-config.xml --> 2<beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:jdbc="http://www.springframework.org/schema/jdbc" 5 xmlns:jee="http://www.springframework.org/schema/jee" 6 xsi:schemaLocation="..."> 7 8 <bean id="transferService" 9 class="com.bank.service.internal.DefaultTransferService"> 10 <constructor-arg ref="accountRepository"/> 11 <constructor-arg ref="feePolicy"/> 12 </bean> 13 14 <bean id="accountRepository" 15 class="com.bank.repository.internal.JdbcAccountRepository"> 16 <constructor-arg ref="dataSource"/> 17 </bean> 18 19 <bean id="feePolicy" 20 class="com.bank.service.internal.ZeroFeePolicy"/> 21 22 <beans profile="dev"> 23 <jdbc:embedded-database id="dataSource"> 24 <jdbc:script 25 location="classpath:com/bank/config/sql/schema.sql"/> 26 <jdbc:script 27 location="classpath:com/bank/config/sql/test-data.sql"/> 28 </jdbc:embedded-database> 29 </beans> 30 31 <beans profile="production"> 32 <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/> 33 </beans> 34 35 <beans profile="default"> 36 <jdbc:embedded-database id="dataSource"> 37 <jdbc:script 38 location="classpath:com/bank/config/sql/schema.sql"/> 39 </jdbc:embedded-database> 40 </beans> 41 42</beans>
1@ExtendWith(SpringExtension.class) 2// ApplicationContext will be loaded from "classpath:/app-config.xml" 3@ContextConfiguration("/app-config.xml") 4@ActiveProfiles("dev") 5class TransferServiceTest { 6 7 @Autowired 8 TransferService transferService; 9 10 @Test 11 void testTransferService() { 12 // test the transferService 13 } 14}
1@ExtendWith(SpringExtension::class) 2// ApplicationContext will be loaded from "classpath:/app-config.xml" 3@ContextConfiguration("/app-config.xml") 4@ActiveProfiles("dev") 5class TransferServiceTest { 6 7 @Autowired 8 lateinit var transferService: TransferService 9 10 @Test 11 fun testTransferService() { 12 // test the transferService 13 } 14}
TransferServiceTest가 실행되면, 그 ApplicationContext는 classpath의 root에 있는
app-config.xml 설정 파일로부터 로딩됩니다. app-config.xml을 살펴보면,
accountRepository bean이 dataSource bean에 대한 의존성을 가지고 있음을 알 수 있습니다.
그러나 dataSource는 최상위 bean으로 정의되어 있지 않습니다. 대신,
dataSource는 production 프로필, dev 프로필, 그리고 default 프로필 내에서
세 번 정의되어 있습니다.
TransferServiceTest에 @ActiveProfiles("dev")를 어노테이션으로 지정함으로써,
Spring TestContext Framework에게 활성 프로필이 {"dev"}로 설정된 ApplicationContext를
로딩하도록 지시합니다. 그 결과, 임베디드 데이터베이스가 생성되고 test 데이터로 채워지며,
accountRepository bean은 개발 DataSource에 대한 참조로 와이어링됩니다.
이는 integration test에서 우리가 원하는 것일 가능성이 큽니다.
bean을 default 프로필에 할당하는 것이 유용한 경우가 종종 있습니다.
default 프로필 내의 bean은 다른 프로필이 명시적으로 활성화되지 않았을 때만 포함됩니다.
이를 사용하여 애플리케이션의 기본 상태에서 사용되는 “fallback” bean을 정의할 수 있습니다.
예를 들어, dev와 production 프로필에 대해 데이터 소스를 명시적으로 제공할 수 있지만,
이 둘 중 어느 것도 활성화되지 않았을 때는 기본값으로 인메모리 데이터 소스를 정의할 수 있습니다.
다음 코드 목록은 XML 대신 @Configuration 클래스를 사용하여 동일한 설정과
integration test를 구현하는 방법을 보여 줍니다:
1@Configuration 2@Profile("dev") 3public class StandaloneDataConfig { 4 5 @Bean 6 public DataSource dataSource() { 7 return new EmbeddedDatabaseBuilder() 8 .setType(EmbeddedDatabaseType.HSQL) 9 .addScript("classpath:com/bank/config/sql/schema.sql") 10 .addScript("classpath:com/bank/config/sql/test-data.sql") 11 .build(); 12 } 13}
1@Configuration 2@Profile("dev") 3class StandaloneDataConfig { 4 5 @Bean 6 fun dataSource(): DataSource { 7 return EmbeddedDatabaseBuilder() 8 .setType(EmbeddedDatabaseType.HSQL) 9 .addScript("classpath:com/bank/config/sql/schema.sql") 10 .addScript("classpath:com/bank/config/sql/test-data.sql") 11 .build() 12 } 13}
1@Configuration 2@Profile("production") 3public class JndiDataConfig { 4 5 @Bean(destroyMethod="") 6 public DataSource dataSource() throws Exception { 7 Context ctx = new InitialContext(); 8 return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); 9 } 10}
1@Configuration 2@Profile("production") 3class JndiDataConfig { 4 5 @Bean(destroyMethod = "") 6 fun dataSource(): DataSource { 7 val ctx = InitialContext() 8 return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource 9 } 10}
1@Configuration 2@Profile("default") 3public class DefaultDataConfig { 4 5 @Bean 6 public DataSource dataSource() { 7 return new EmbeddedDatabaseBuilder() 8 .setType(EmbeddedDatabaseType.HSQL) 9 .addScript("classpath:com/bank/config/sql/schema.sql") 10 .build(); 11 } 12}
1@Configuration 2@Profile("default") 3class DefaultDataConfig { 4 5 @Bean 6 fun dataSource(): DataSource { 7 return EmbeddedDatabaseBuilder() 8 .setType(EmbeddedDatabaseType.HSQL) 9 .addScript("classpath:com/bank/config/sql/schema.sql") 10 .build() 11 } 12}
1@Configuration 2public class TransferServiceConfig { 3 4 @Autowired DataSource dataSource; 5 6 @Bean 7 public TransferService transferService() { 8 return new DefaultTransferService(accountRepository(), feePolicy()); 9 } 10 11 @Bean 12 public AccountRepository accountRepository() { 13 return new JdbcAccountRepository(dataSource); 14 } 15 16 @Bean 17 public FeePolicy feePolicy() { 18 return new ZeroFeePolicy(); 19 } 20}
1@Configuration 2class TransferServiceConfig { 3 4 @Autowired 5 lateinit var dataSource: DataSource 6 7 @Bean 8 fun transferService(): TransferService { 9 return DefaultTransferService(accountRepository(), feePolicy()) 10 } 11 12 @Bean 13 fun accountRepository(): AccountRepository { 14 return JdbcAccountRepository(dataSource) 15 } 16 17 @Bean 18 fun feePolicy(): FeePolicy { 19 return ZeroFeePolicy() 20 } 21}
1@SpringJUnitConfig({ 2 TransferServiceConfig.class, 3 StandaloneDataConfig.class, 4 JndiDataConfig.class, 5 DefaultDataConfig.class}) 6@ActiveProfiles("dev") 7class TransferServiceTest { 8 9 @Autowired 10 TransferService transferService; 11 12 @Test 13 void testTransferService() { 14 // test the transferService 15 } 16}
1@SpringJUnitConfig( 2 TransferServiceConfig::class, 3 StandaloneDataConfig::class, 4 JndiDataConfig::class, 5 DefaultDataConfig::class) 6@ActiveProfiles("dev") 7class TransferServiceTest { 8 9 @Autowired 10 lateinit var transferService: TransferService 11 12 @Test 13 fun testTransferService() { 14 // test the transferService 15 } 16}
이 변형에서 우리는 XML 설정을 네 개의 독립적인 @Configuration 클래스로 분리했습니다:
TransferServiceConfig: @Autowired를 사용한 의존성 주입을 통해 dataSource를 획득합니다.StandaloneDataConfig: 개발자 test에 적합한 임베디드 데이터베이스를 위한 dataSource를 정의합니다.JndiDataConfig: 운영 환경에서 JNDI로부터 조회되는 dataSource를 정의합니다.DefaultDataConfig: 어떤 프로필도 활성 상태가 아닐 경우를 대비해 기본 임베디드 데이터베이스를 위한
dataSource를 정의합니다.XML 기반 설정 예제와 마찬가지로, 여전히 TransferServiceTest에
@ActiveProfiles("dev")를 어노테이션으로 지정하지만, 이번에는 @ContextConfiguration
어노테이션을 사용하여 네 개의 설정 클래스를 모두 지정합니다.
test 클래스 본문 자체는 완전히 변경되지 않은 상태로 남습니다.
하나의 프로필 집합이 주어진 프로젝트 내의 여러 test 클래스에서 사용되는 경우가
자주 있습니다. 따라서 @ActiveProfiles 어노테이션의 중복 선언을 피하기 위해,
base 클래스에 한 번 @ActiveProfiles를 선언하고, subclass가 base 클래스로부터
자동으로 @ActiveProfiles 설정을 상속받도록 할 수 있습니다.
다음 예제에서 @ActiveProfiles 선언(및 다른 어노테이션들)은 추상 superclass인
AbstractIntegrationTest로 이동되었습니다:
Test 설정은 enclosing 클래스로부터도 상속될 수 있습니다. 자세한 내용은
@Nestedtest 클래스 설정을 참조하십시오.
1@SpringJUnitConfig({ 2 TransferServiceConfig.class, 3 StandaloneDataConfig.class, 4 JndiDataConfig.class, 5 DefaultDataConfig.class}) 6@ActiveProfiles("dev") 7abstract class AbstractIntegrationTest { 8}
1@SpringJUnitConfig( 2 TransferServiceConfig::class, 3 StandaloneDataConfig::class, 4 JndiDataConfig::class, 5 DefaultDataConfig::class) 6@ActiveProfiles("dev") 7abstract class AbstractIntegrationTest { 8}
1// "dev" profile inherited from superclass 2class TransferServiceTest extends AbstractIntegrationTest { 3 4 @Autowired 5 TransferService transferService; 6 7 @Test 8 void testTransferService() { 9 // test the transferService 10 } 11}
1// "dev" profile inherited from superclass 2class TransferServiceTest : AbstractIntegrationTest() { 3 4 @Autowired 5 lateinit var transferService: TransferService 6 7 @Test 8 fun testTransferService() { 9 // test the transferService 10 } 11}
@ActiveProfiles는 또한 다음 예에서 보듯이 활성 프로필 상속을 비활성화하는 데 사용할 수 있는
inheritProfiles 속성도 지원합니다:
1// "dev" profile overridden with "production" 2@ActiveProfiles(profiles = "production", inheritProfiles = false) 3class ProductionTransferServiceTest extends AbstractIntegrationTest { 4 // test body 5}
1// "dev" profile overridden with "production" 2@ActiveProfiles("production", inheritProfiles = false) 3class ProductionTransferServiceTest : AbstractIntegrationTest() { 4 // test body 5}
또한, 때로는 test에 대한 활성 프로필을 선언적으로가 아니라 프로그래밍 방식으로 resolve해야 할 필요가 있습니다. 예를 들어 다음과 같은 기준에 따라 그럴 수 있습니다:
활성 bean definition 프로필을 프로그래밍 방식으로 resolve하기 위해,
custom ActiveProfilesResolver를 구현하고 @ActiveProfiles의 resolver
속성을 사용하여 이를 등록할 수 있습니다. 더 자세한 정보는 해당
javadoc을
참조하십시오.
다음 예제는 custom OperatingSystemActiveProfilesResolver를 구현하고
등록하는 방법을 보여 줍니다:
1// "dev" profile overridden programmatically via a custom resolver 2@ActiveProfiles( 3 resolver = OperatingSystemActiveProfilesResolver.class, 4 inheritProfiles = false) 5class TransferServiceTest extends AbstractIntegrationTest { 6 // test body 7}
1// "dev" profile overridden programmatically via a custom resolver 2@ActiveProfiles( 3 resolver = OperatingSystemActiveProfilesResolver::class, 4 inheritProfiles = false) 5class TransferServiceTest : AbstractIntegrationTest() { 6 // test body 7}
1public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver { 2 3 @Override 4 public String[] resolve(Class<?> testClass) { 5 String profile = ...; 6 // determine the value of profile based on the operating system 7 return new String[] {profile}; 8 } 9}
1class OperatingSystemActiveProfilesResolver : ActiveProfilesResolver { 2 3 override fun resolve(testClass: Class<*>): Array<String> { 4 val profile: String = ... 5 // determine the value of profile based on the operating system 6 return arrayOf(profile) 7 } 8}
Context Configuration Inheritance
Context Configuration with Test Property Sources