이 chapter에서는 통합 테스트에 대한 Spring의 지원과 단위 테스트에 대한 모범 사례를 다룹니다. Spring 팀은 TDD(테스트 주도 개발)를 지지합니다. Spring 팀은 IoC(inversion of control)를 올바르게 사용하면 확실히 단위 테스트와 통합 테스트가 더 쉬워진다는 것을 발견했습니다. (class에 setter method와 적절한 constructor가 있으면 테스트에서 연결할 필요없이 쉽게 연결할 수 있다는 점에서 service locator registry 와 유사한 구조 설정)
Unit Testing
Dependency injection은 기존 Java EE 개발보다 코드가 container에 덜 의존하도록 해야 합니다.
application을 구성하는 POJO는 JUnit 또는 TestNG 테스트에서 테스트 할 수 있어야 하며 Spring이나 다른 container없이 new
operator를 사용하여 인스턴스화 된 객체가 있어야 합니다.
코드를 분리하여 테스트 하기 위해 (다른 중요한 테스트 기법과 연계하여) mock objects를 사용할 수 있습니다.
Spring에 대한 architecture 권장사항을 따르는 경우 codebase의 깔끔한 계층화 및 component화를 통해 더 쉽게 단위 테스트를 수행할 수 있습니다.
예를 들어 단위 테스트를 실행하는 동안 persistent data에 접근할 필요없이 DAO 또는 repository interface를 stubbing 또는 mocking 하여 service layer를 테스트할 수 있습니다.
설정할 runtime infrastructure가 없기 때문에 실제 단위 테스트는 일반적으로 매우 빠르게 실행됩니다. 개발 방식에 단위 테스트를 도입하면 생산성을 높일 수 있습니다. Ioc 기반 application을 위한 효과적인 단위 테스트 작성을 돕기 위해 testing chapter의 이 section이 필요하지 않을 수 있습니다. 단, 특정 단위 테스트 시나리오의 경우 Spring Framework는 이 chapter에서 설명하는 mock object와 testing support class를 제공합니다.
Mock Objects
Spring 은 mock 처리를 위한 다수의 package를 포함하고 있습니다:
Environment
org.springframework.mock.env
package는 Environment
및 PropertySource
추상화의 mock 구현을 포함하고 있습니다.
(Bean Definition Profiles 및 PropertySource
Abstraction 참조)
MockEnvironment
and MockPropertySource
는 환경별 특성에 따라 달라지는 코드에 대한 out-of-container 테스트를 개발하는데 유용합니다.
JNDI
org.springframework.mock.jndi
package는 test suite 또는 stand-alone application을 위한 간단한 JNDI 환경을 구성하는데 사용할 수 있는 JNDI SPI의 일부 구현이 표함되어 있습니다.
예를 들어 JDBC DataSource
instance가 Java EE container에서와 같이 테스트 코드에서 동일한 JNDI 이름에 바인딩되면 수정없이 테스트 시나리오에서 application 코드와 구성을 모두 재사용할 수 있습니다.
org.springframework.mock.jndi package의 mock JNDI 지원은 Simple-JNDI와 같은 third party의 완전한 솔루션을 위해 Spring Framework 5.2에서 공식적으로 deprecated되었습니다.
|
Servlet API
org.springframework.mock.web
package는 web context, controller, filter를 테스트하는데 유용한 Servlet API mock object를 포함하고 있습니다.
이 mock object는 Spring의 Web MVC framework 사용을 대상으로 하며 일반적으로 dynamic mock object (EasyMock 같은) 또는 alternative Servlet API mock object(MockObjects 같은) 보다 더 사용하기 편리합니다.
Spring Framework 5.0부터 org.springframework.mock.web 의 mock object는 Servlet 4.0 API를 기반으로 합니다.
|
Spring MVC Test framework는 Spring MVC를 위한 integration testing framework를 제공하기 위해 mock Servlet API object를 기반으로 합니다. MockMvc를 참조하세요.
Spring Web Reactive
org.springframework.mock.http.server.reactive
package는 WebFlux application에 사용하는 ServerHttpRequest
및 ServerHttpResponse
의 mock 구현을 포함하고 있습니다.
org.springframework.mock.web.server
package는 mock request 및 response object에 대한 mock ServerWebExchange
를 포함하고 있습니다.
MockServerHttpRequest
및 MockServerHttpResponse
는 둘다 서버별 구현 및 동작을 공유하는 같은 abstract 기반 class에서 extend 되었습니다.
예를 들어 mock request는 생성된 후에는 변경할 수 없지만 ServerHttpRequest
로부터 mutate()
method를 사용하여 수정된 instance를 만들 수 있습니다.
mock response가 write contract를 올바르게 구현하고 completion handle( Mono<Void>
)을 반환하기 위해 기본적으로 cache().then()
와 함께 Flux
를 사용하여 data를 버퍼링하고 테스트에서 assertion을 사용할 수 있게 합니다.
application은 custom write function을 설정할 수 있습니다. (예를 들면 infinite stream 테스트)
WebTestClient 는 HTTP server없이 WebFlux application을 테스트하기 위한 지원을 제공하기 위해 mock request 및 response를 기반으로 합니다. client는 또한 실행중인 server와 end-to-end 테스트에도 사용할 수 있습니다.
Unit Testing Support Classes
Spring에는 단위 테스트에 도움이 될 수 있는 여러 class가 포함되어 있습니다. 두 가지 범주로 나뉩니다:
General Testing Utilities
org.springframework.test.util
package에는 단위 및 통합 테스트에 사용하기 위한 몇 가지 범용 유틸리티가 포함되어 있습니다.
ReflectionTestUtils
은 reflection 기반 utility method의 모음입니다.
constant value를 변경하거나, non-public
field를 설정하거나 non-public
setter method를 호출하거나 non-public
configuration을 호출하거나 application code를 다음과 같이 lifecycle callback method를 호출해야 하는 테스트 시나리오에서 이러한 method를 사용할 수 있습니다:
-
domain entity의 propery에 대한
public
setter method 와 달리private
orprotected
field access를 허용하는 ORM framework (JPA 와 Hibernate 같은) -
private
또는protected
field, setter method, configuration method에 대한 dependency injection을 제공하는 Spring 의 annotation 지원 (@Autowired
,@Inject
및@Resource
같은) -
lifecycle callback method를 위한
@PostConstruct
및@PreDestroy
annotation 사용
AopTestUtils
은 AOP 관련 utility method의 모음입니다.
이 method를 사용하여 하나 이상의 Spring proxy 뒤에 숨겨진 기본 대상 object에 대한 참조를 얻을 수 있습니다.
예를 들어 EasyMock 또는 Mockito와 같은 라이브러리를 사용하여 bean을 dynamic mock을 구성하고 Spring proxy로 wrapping 된 경우 기본 mock에 직접 access해 예상을 구성하고 검증을 수행해야 할 수 있습니다.
Spring의 core AOP utility에 대해서는 AopUtils
및 AopProxyUtils
를 참조하세요.
Spring MVC Testing Utilities
org.springframework.test.web
package는 JUnit, TestNG 또는 다른 testing framework와 결함해 Spring MVC ModelAndView
objects와 함께 단위 테스트 하는데 사용할 수 있는 ModelAndViewAssert
를 포함하고 있습니다.
Unit testing Spring MVC Controllers
Spring MVC Controller class를 POJO로 단위 테스트하려면 MockHttpServletRequest , MockHttpSession Spring의 Servlet API mocks 등이 결합된 ModelAndViewAssert 를 사용합니다.
Spring MVC에 대한 WebApplicationContext configuration과 함께 Spring MVC 및 REST Controller class의 통합 테스트를 하려면 Spring MVC Test Framework을 사용하세요.
|
Integration Testing
이 section(이 chapter의 나머지 대부분)은 Spring application에 대한 통합 테스트를 다룹니다. 여기에는 다음 항목이 포함됩니다:
Overview
application server에 배포하거나 enterprise infrastructure에 연결하지 않고도 일부 통합 테스트를 수행할 수 있어야 합니다. 이렇게 하면 다음과 같은 사항을 테스트할 수 있습니다:
-
Spring IoC container context의 올바른 연결
-
JDBC 또는 ORM 도구를 사용한 Data access. 여기에는 SQL statement의 정확성, Hibernate queryt, JPA entity mapping 등이 포함될 수 있습니다.
Spring Framework는 spring-test
module에서 통합 테스트를 위한 first-class support를 제공합니다.
실제 JAR file의 이름에는 release version이 포함될 수 있으며 어디에서 얻느냐에 따라 긴 org.springframework.test
형식일 수 있습니다. (section on Dependency Management 설명 참조)
이 library에는 Spring container와의 통합 테스트를 위한 유용한 class가 포함된 org.springframework.test
package가 포함되어 있습니다.
이 테스트는 application server 또는 기타 배포 환경에 의존하지 않습니다.
이러한 테스트는 단위 테스트보다 실행 속도가 느리지만 application server에 대한 배포에 의존하는 동등한 Selenium 테스트 또는 원격 테스트보다 훨씬 빠릅니다.
단위 및 통합 테스트 지원은 annotation 기반 Spring TestContext Framework 형태로 제공됩니다. 이 TestContext framework는 사용 중인 실제 test framework와 무관하며, 이를 통해 JUnit, TestNG 등 다양한 환경에서 테스트를 계측할 수 있습니다.
Goals of Integration Testing
Spring의 통합 테스트 지원에는 다음과 같은 주요 목표가 있습니다:
-
테스트 간 Spring IoC container caching을 관리
-
통합 테스트에 적합한 transaction management 제공
-
통합 테스트 작성 시 개발자를 지원하는 Spring-specific base class 제공
다음 몇 section에서는 각 목표를 설명하고 구현 및 구성 세부 정보에 대한 링크를 제공합니다.
Context Management and Caching
Spring TestContext Framework는 Spring ApplicationContext
intance 및 WebApplicationContext
instance의 일관된 로드와 해당 context의 caching을 제공합니다.
startup time이 이슈가 될 수 있기 때문에 로드된 context의 caching에 대한 지원은 중요합니다.
Spring 자체의 overhead 때문이 아니라 Spring container에 의해 인스턴스화 된 객체가 인스턴스화 하는데 시간이 걸리기 때문입니다.
예를 들어 50 ~ 100개의 Hibernate mapping file이 있는 프로젝트는 mapping file을 로드하는데 10 ~ 20초가 걸릴 수 있으며 모든 test fixture에서 모든 테스트를 실행하기 전에 해당 비용이 발생하면 전체 테스트 실행이 느려지고 개발자 생산성이 감소합니다.
test class는 일반적으로 XML 또는 Groovy configuration metadata에 대한 resource location 배열 (종종 classpath에 있음) 또는 application을 구성하는데 사용되는 component class 배열을 선언합니다.
이러한 location 또는 class는 product 배포를 위해 web.xml
또는 다른 configuration file에 지정된 것과 동일하거나 유사합니다.
기본적으로 한번 로드되면 구성된 ApplicationContext
는 각 테스트에 재사용됩니다.
따라서 설정 비용은 test suite 당 한번만 발생하며 후속 테스트 실행이 훨씬 빠릅니다.
이 멕락에서 “test suite” 라는 용어는 동일한 JVM에서 실행되는 모든 테스트를 의미합니다.
예를 들어 모든 테스트는 주어진 project 또는 module에 대해 Ant, Maven 또는 Gradle에서 실행됩니다.
테스트가 application context를 손상 시키고 다시 로드해야 하는 경우 (예를 들면 bean 정의 또는 application object의 상태를 수정한 경우) TestContext framework를 구성하여 다음을 실행하기 전에 구성을 다시 로드하고 application context를 다시 빌드할 수 있습니다.
TestContext framework를 사용한 Context Management 및 Context Caching을 참조하세요.
Dependency Injection of Test Fixtures
TextContext framework가 application context를 로드할 때 선택적으로 dependency injection을 사용하여 test class의 instance를 구성할 수 있습니다.
이는 application context에서 사전 구성된 bean을 사용하여 test fixture를 설정하는 편리한 메커니즘을 제공합니다.
여기에서 강력한 이점은 다양한 테스트 시나리오 (예: spring-managed object graph, transactional proxy, DataSource
instance 등을 구성하는 경우)에서 application context를 재사용 할 수 있으므로 개별 테스트 케이스에 대해 복잡한 test fixture 설정을 복제할 필요가 없다는 것입니다.
예를 들어 Title
domain entity에 대한 data access logic 구현 class ( HibernateTitleRepository
)가 있는 시나리오를 생각해보십시오.
다음 영역을 테스트하는 통합 테스트를 작성하려고 합니다:
-
Spring configuration : 기본적으로
HibernateTitleRepository
bean 구성과 관련된 모든 것이 정확하고 존재합니까? -
Hibernate mapping file configuration: 모든 것이 올바르게 맵핑되고 올바른 지연 로딩 설정이 제자리에 있습니까?
-
HibernateTitleRepository
logic : 이 class의 구성된 인스턴스가 예상대로 작동합니까?
TestContext framework를 사용하여 test fixture의 dependency injection을 참조하세요.
Transaction Management
실제 database에 access하는 테스트의 일반적인 문제 중 하나는 지속성 저장소의 상태에 미치는 영향입니다. 개발 database를 사용하는 경우에도 상태 변경은 향후 테스트에 영향을 미칠 수 있습니다. 또한 영구 데이터 insert, update와 같은 작업은 transaction 외부에서 수행 (또는 확인) 할 수 없습니다.
TestContext framework는 이 문제를 해결합니다.
기본적으로 framework는 각 테스트에 대해 transaction을 만들고 rollback합니다.
transcation의 존재를 가정할 수 있는 코드를 작성할 수 있습니다.
테스트에서 transaction으로 proxy 된 object를 호출하면 구성된 transaction semantic에 따라 올바르게 동작합니다.
또한 테스트를 위해 관리되는 transaction 내에서 실행되는 동안 테스트 method가 선택한 테이블의 내용을 삭제하면 transaction이 기본적으로 rollback되고 database는 테스트 실행 이전으로 돌아갑니다.
transaction 지원은 테스트의 application context에 정의된 PlatformTransactionManager
bean을 사용하여 테스트에 제공됩니다.
transaction을 commit 하려면 (비정상적이지만 때로는 특정 테스트가 database를 채우거나 수정하려는 경우 유용함) @Commit
annotation을 사용하여 transaction이 commit 되도록 TestContext framework에 지시할 수 있습니다.
TestContext framework로 transaction management를 참조하세요.
Support Classes for Integration Testing
Spring TestContext Framework는 통합 테스트 작성을 단순화하는 여러 abstract
지원 class를 제공합니다.
이러한 기본 test class는 test framework에 대한 잘 정의된 hook과 편리한 instance variable 및 method를 제공하여 다음에 엑세스 할 수 있습니다:
-
ApplicationContext
, 명시적인 bean 검색을 수행하거나 전체 context의 상태를 테스트합니다. -
JdbcTemplate
, SQL 문을 실행하여 데이터베이스를 쿼리합니다. 이러한 쿼리를 사용하여 데이터베이스 관련 application 코드 실행 전후에 데이터베이스 상태를 확인할 수 있으며 Spring은 이러한 쿼리가 application 코드와 동일한 transaction 범위에서 실행되도록 합니다. ORM 도구와 함께 사용하는 경우 오탐지를 피해야 합니다.
또한 project에 특정한 instance variable 및 method를 사용하여 application 전체의 사용자 지정 superclass를 만들 수도 있습니다.
TestContext framework에 대한 지원 class를 참조하세요.
JDBC Testing Support
org.springframework.test.jdbc
package에는 표준 dastabase 테스트 시나리오를 단순화하기 위한 JDBC 관련 utility function인 JdbcTestUtils
가 포함되어 있습니다.
특히 JdbcTestUtils
은 다음과 같은 static utility method를 제공합니다.
-
countRowsInTable(..)
: 주어진 테이블의 행 수를 계산합니다. -
countRowsInTableWhere(..)
: 제공된WHERE
절을 사용하여 주어진 테이블의 행 수를 계산합니다. -
deleteFromTables(..)
: 지정된 테이블에서 모든 행을 삭제합니다. -
deleteFromTableWhere(..)
: 제공된WHERE
절을 사용하여 주어진 테이블에서 행을 삭제합니다. -
dropTables(..)
: 지정된 테이블을 삭제합니다.
|
Annotations
이 section은 Spring application을 테스트 할 때 사용할 수 있는 annotation을 다룹니다. 여기에는 다음 항목이 포함됩니다:
Spring Testing Annotations
Spring Framework는 TestContext framework와 함께 단위 및 통합 테스트에서 사용할 수 있는 다음과 같은 Spring 관련 annotation set을 제공합니다. default attribute, attribute alias 및 기타 세부 사항을 포함한 추가 정보는 해당 javadoc을 참조하세요.
Spring의 test annotation에는 다음이 포함됩니다:
@BootstrapWith
@BootstrapWith
는 Spring TestContext Framework가 bootstrap되는 방법을 구성하는데 사용할 수 있는 class-level annotation 입니다.
특히 @BootstrapWith
를 사용하여 custom TestContextBootstrapper
를 지정합니다.
자세한 내용은 bootstrapping the TestContext framework section을 참조하세요.
@ContextConfiguration
@ContextConfiguration
은 통합 테스트를 위해 ApplicationContext
를 로드하고 구성하는 방법을 결정하는데 사용되는 class-level metadata를 정의합니다.
특히 @ContextConfiguration
는 context를 로드하는데 사용되는 application context resource locations
또는 component classes
를 선언합니다.
Resource locations은 일반적으로 XML configuration file 또는 Groovy script이며 component class는 일반적으로 @Configuration
class입니다.
그러나 resource locastion은 file system의 file 및 script를 참조할 수도 있으며 component class는 @Component
classes, @Service
classes 등이 될 수 있습니다.
자세한 내용은 Component Classes를 참조하세요.
다음 예제는 xml file을 참조하는 @ContextConfiguration
annotation을 보여줍니다:
@ContextConfiguration("/test-config.xml") (1)
class XmlApplicationContextTests {
// class body...
}
1 | XML file을 참조합니다. |
@ContextConfiguration("/test-config.xml") (1)
class XmlApplicationContextTests {
// class body...
}
1 | XML file을 참조합니다. |
다음 예제는 class를 참조하는 @ContextConfiguration
annotation을 보여줍니다:
@ContextConfiguration(classes = TestConfig.class) (1)
class ConfigClassApplicationContextTests {
// class body...
}
1 | class를 참조합니다. |
@ContextConfiguration(classes = [TestConfig::class]) (1)
class ConfigClassApplicationContextTests {
// class body...
}
1 | class를 참조합니다. |
대안으로 resource location 또는 component class를 선언하는 것 외에 @ContextConfiguration
를 사용하여 ApplicationContextInitializer
class를 선언할 수 있습니다.
다음 예는 이러한 경우를 보여줍니다:
@ContextConfiguration(initializers = CustomContextIntializer.class) (1)
class ContextInitializerTests {
// class body...
}
@ContextConfiguration(initializers = [CustomContextIntializer::class]) (1)
class ContextInitializerTests {
// class body...
}
1 | Declaring an initializer class. |
선택적으로 @ContextConfiguration
를 사용하여 ContextLoader
strategy를 선언할 수도 있습니다.
그러나 default loader가 initializers
및 resource locations
또는 component classes
를 지원하므로 일반적으로 loader를 명시적으로 구성할 필요가 없습니다.
다음 예제에서는 location과 loader를 모두 사용합니다:
@ContextConfiguration(locations = "/test-context.xml", loader = CustomContextLoader.class) (1)
class CustomLoaderXmlApplicationContextTests {
// class body...
}
1 | location과 custom loader를 모두 구성합니다. |
@ContextConfiguration("/test-context.xml", loader = CustomContextLoader::class) (1)
class CustomLoaderXmlApplicationContextTests {
// class body...
}
1 | location과 custom loader를 모두 구성합니다. |
@ContextConfiguration 는 resource location 또는 configuration class의 상속은 물론 superclass 또는 enclosing class에 의해 선언된 context initializer를 제공합니다.
|
자세한 내용은 Context Management, @Nested
test class configuration, 및 @ContextConfiguration
javadoc을 참조하세요.
@WebAppConfiguration
@WebAppConfiguration
은 통합 테스트를 위해 로드된 ApplicationContext
가 WebApplicationContext
여야 함을 선언하는데 사용할 수 있는 class-level annotation 입니다.
@WebAppConfiguration
이 test class에 존재하기만 하면 web application의 root(resource base path)에 대한 "file:src/main/webapp"
default value를 사용하여 WebApplicationContext
가 테스트용으로 로드됩니다.
resource base path는 테스트의 WebApplicationContext
에 대한 ServletContext
역할을 하는 MockServletContext
를 만드는데 사용됩니다.
다음 예는 @WebAppConfiguration
annotation을 사용하는 방법을 보여줍니다:
@ContextConfiguration
@WebAppConfiguration (1)
class WebAppTests {
// class body...
}
@ContextConfiguration
@WebAppConfiguration (1)
class WebAppTests {
// class body...
}
1 | @WebAppConfiguration annotation. |
기본 값을 재정의 하려면 implicit value
attribute를 사용하여 다른 base resource path를 지정할 수 있습니다.
classpath:
및 file:
resource prefix가 지원됩니다.
resource prefix가 제공되지 않으면 path는 file system resource로 간주됩니다.
다음 예는 classpath resource를 지정하는 방법을 보여줍니다:
@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources") (1)
class WebAppTests {
// class body...
}
1 | classpath resource 지정. |
@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources") (1)
class WebAppTests {
// class body...
}
1 | classpath resource 지정. |
@WebAppConfiguration
는 single test class 또는 test class hierarchy 내에서 @ContextConfiguration
와 함께 사용해야 합니다.
자세한 내용은 @WebAppConfiguration
javadoc을 참조하세요.
@ContextHierarchy
@ContextHierarchy
는 통합 테스트를 위한 ApplicationContext
instance의 hierarchy를 정의하는데 사용되는 class-level annotation 입니다.
@ContextHierarchy
는 하나 이상의 @ContextConfiguration
instances 목록으로 선언되어야 하며, 각 instance는 context hierarchy의 level을 정의합니다.
다음 예는 single test class내에서 @ContextHierarchy
의 사용을 보여줍니다. (@ContextHierarchy
는 test class hierarchy 내에서도 사용할 수 있음):
@ContextHierarchy({
@ContextConfiguration("/parent-config.xml"),
@ContextConfiguration("/child-config.xml")
})
class ContextHierarchyTests {
// class body...
}
@ContextHierarchy(
ContextConfiguration("/parent-config.xml"),
ContextConfiguration("/child-config.xml"))
class ContextHierarchyTests {
// class body...
}
@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(classes = AppConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
class WebIntegrationTests {
// class body...
}
@WebAppConfiguration
@ContextHierarchy(
ContextConfiguration(classes = [AppConfig::class]),
ContextConfiguration(classes = [WebConfig::class]))
class WebIntegrationTests {
// class body...
}
test class hierarchy 내에서 지정된 level에 대한 context hierarchy 구성을 병합하거나 재정의해야하는 경우 class hierarchy의 각 해당 level에서 @ContextConfiguration
의 name
attribute에 동일한 값을 제공하여 해당 level의 name을 명시적으로 지정해야 합니다.
추가 예제는 Context Hierarchies 및 @ContextHierarchy
javadoc을 참조하세요.
@ActiveProfiles
@ActiveProfiles
통합 테스트를 위해 ApplicationContext
을 로드할 때 활성화 되어야 하는 bean definition profile을 선언하는데 사용되는 class-level annotation 입니다.
다음 예는 dev
profile이 활성화 되어야 함을 나타냅니다:
@ContextConfiguration
@ActiveProfiles("dev") (1)
class DeveloperTests {
// class body...
}
1 | dev profile이 활성화 되어야 함을 나타냅니다. |
@ContextConfiguration
@ActiveProfiles("dev") (1)
class DeveloperTests {
// class body...
}
1 | dev profile이 활성화 되어야 함을 나타냅니다. |
다음 예는 dev
및 integration
profile이 모두 활성화되어야 함을 나타냅니다:
@ContextConfiguration
@ActiveProfiles({"dev", "integration"}) (1)
class DeveloperIntegrationTests {
// class body...
}
1 | dev 및 integration profile이 활성화되어야 함을 나타냅니다. |
@ContextConfiguration
@ActiveProfiles(["dev", "integration"]) (1)
class DeveloperIntegrationTests {
// class body...
}
1 | dev 및 integration profile이 활성화되어야 함을 나타냅니다. |
@ActiveProfiles 기본적으로 superclass 및 enclosing class에 의해 선언된 active bean profile 상속을 지원합니다.
또한 custom ActiveProfilesResolver 를 구현하고 @ActiveProfiles 의 resolver attribute를 사용하여 등록함으로써 active bean definition profile을 programming 방식으로 해결할 수 있습니다.
|
자세한 내용은 Context Configuration with Environment Profiles, @Nested
test class configuration 및 @ActiveProfiles
javadoc을 참조하세요.
@TestPropertySource
@TestPropertySource
는 통합 테스트를 위해 로드된 ApplicationContext
에 대해 Environment
안에서 PropertySources
set에 추가할 properties file과 inline property의 localtion을 구성하는데 사용할 수 있는 class-level annotation 입니다.
다음 예제는 classpath에서 properties file을 선언하는 방법을 보여줍니다;
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
// class body...
}
1 | classpath의 root에 있는 test.properties 에서 property를 가져옵니다. |
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
// class body...
}
1 | classpath의 root에 있는 test.properties 에서 property를 가져옵니다. |
다음 예제는 inline property를 선언하는 방법을 보여줍니다:
@ContextConfiguration
@TestPropertySource(properties = { "timezone = GMT", "port: 4242" }) (1)
class MyIntegrationTests {
// class body...
}
1 | timezone 및 port property 선언. |
@ContextConfiguration
@TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) (1)
class MyIntegrationTests {
// class body...
}
1 | timezone 및 port property 선언. |
예제 및 자세한 내용은 Context Configuration with Test Property Sources를 참조하세요.
@DynamicPropertySource
@DynamicPropertySource
는 통합 테스트를 위해 로드된 ApplicationContext
에 대해 Environment
에서 PropertySources
set에 추가할 dynamic property를 등록할 때 사용할 수 있는 method-level annotation 입니다.
Dynamic property는 property의 값을 미리 모르는 경우 유용합니다.
예를 들어 Testcontainers project에서 관리하는 container와 같은 외부 resource에서 property를 관리하는 경우입니다.
다음 예제는 dynamic property를 등록하는 방법을 보여줍니다:
@ContextConfiguration
class MyIntegrationTests {
static MyExternalServer server = // ...
@DynamicPropertySource (1)
static void dynamicProperties(DynamicPropertyRegistry registry) { (2)
registry.add("server.port", server::getPort); (3)
}
// tests ...
}
1 | @DynamicPropertySource`와 함께 `static method를 annotate 합니다. |
2 | DynamicPropertyRegistry 를 argument 사용합니다. |
3 | server에서 lazy하게 검색할 dynamic server.port property를 등록합니다. |
@ContextConfiguration
class MyIntegrationTests {
companion object {
@JvmStatic
val server: MyExternalServer = // ...
@DynamicPropertySource (1)
@JvmStatic
fun dynamicProperties(registry: DynamicPropertyRegistry) { (2)
registry.add("server.port", server::getPort) (3)
}
}
// tests ...
}
1 | @DynamicPropertySource`와 함께 `static method를 annotate 합니다. |
2 | DynamicPropertyRegistry 를 argument 사용합니다. |
3 | server에서 lazy하게 검색할 dynamic server.port property를 등록합니다. |
자세한 내용은 Context Configuration with Dynamic Property Sources를 참조하세요.
@DirtiesContext
@DirtiesContext
는 테스트 실행 중 기본 Spring ApplicationContext
가 더럽혀졌음(어떤 방식으로 인해 수정되거나 손상되는 경우 — 예를 들어 singleton bean의 상태가 변경됨)을 의미하며 이는 종료가 되어야 합니다.
Application context가 dirty로 표시되면 test framework의 cache에서 제거되고 닫힙니다.
결과적으로 기본 Spring container는 동일한 configuration metadata를 가진 context가 필요한 모든 후속 테스트를 위해 rebuild 됩니다.
동일한 class 또는 class hierarchy 내에서 @DirtiesContext
를 class-level과 method-level annotation으로 모두 사용할 수 있습니다.
이러한 시나리오에서 ApplicationContext
은 구성된 methodMode
와 classMode
에 따라 current test class 전과 후에 annotation이 달린 method가 더러워진 것으로 표시됩니다.
다음 예는 다양한 configuration 시나리오에서 context가 더러워지는 시기를 설명합니다:
-
current test class 전에 class mode가
BEFORE_CLASS
로 설정된 class에 선언되었을 때Java@DirtiesContext(classMode = BEFORE_CLASS) (1) class FreshContextTests { // some tests that require a new Spring container }
1 current test class 이전의 context를 dirty 합니다. Kotlin@DirtiesContext(classMode = BEFORE_CLASS) (1) class FreshContextTests { // some tests that require a new Spring container }
1 current test class 이전의 context를 dirty 합니다. -
current test class 이후 class mode가
AFTER_CLASS
(즉, default class mode)로 설정된 class에 선언되었을 때Java@DirtiesContext (1) class ContextDirtyingTests { // some tests that result in the Spring container being dirtied }
1 current test class 이후의 context를 dirty 합니다. Kotlin@DirtiesContext (1) class ContextDirtyingTests { // some tests that result in the Spring container being dirtied }
1 current test class 이후의 context를 dirty 합니다. -
current test class의 각 test method 이전에 class mode가
BEFORE_EACH_TEST_METHOD
로 설정된 class에 선언되었을 때Java@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) (1) class FreshContextTests { // some tests that require a new Spring container }
1 각 test method 전에 context를 dirty 합니다. Kotlin@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) (1) class FreshContextTests { // some tests that require a new Spring container }
1 각 test method 전에 context를 dirty 합니다. -
current test class 의 각 test method 이후에 class mode가
AFTER_EACH_TEST_METHOD
로 설정된 class에 선언되었을 때Java@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) (1) class ContextDirtyingTests { // some tests that result in the Spring container being dirtied }
1 각 test method 이후 context가 dirty 합니다. Kotlin@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) (1) class ContextDirtyingTests { // some tests that result in the Spring container being dirtied }
1 각 test method 이후 context가 dirty 합니다. -
current test 전에 method mode가 `BEFORE_METHOD`로 설정된 method에 선언된 경우
Java@DirtiesContext(methodMode = BEFORE_METHOD) (1) @Test void testProcessWhichRequiresFreshAppCtx() { // some logic that requires a new Spring container }
1 current test method 전에 context가 dirty 합니다. Kotlin@DirtiesContext(methodMode = BEFORE_METHOD) (1) @Test fun testProcessWhichRequiresFreshAppCtx() { // some logic that requires a new Spring container }
1 current test method 전에 context가 dirty 합니다. -
current test 이후 method mode가
AFTER_METHOD
(즉, default method mode)로 설정된 method에 선언된 경우Java@DirtiesContext (1) @Test void testProcessWhichDirtiesAppCtx() { // some logic that results in the Spring container being dirtied }
1 current test method 이후 context가 dirty 합니다. Kotlin@DirtiesContext (1) @Test fun testProcessWhichDirtiesAppCtx() { // some logic that results in the Spring container being dirtied }
1 current test method 이후 context가 dirty 합니다.
context가 @ContextHierarchy
로 context hierarchy의 일부로 구성된 테스트에서 @DirtiesContext
를 사용하면 hierarchyMode
flag를 사용하여 context cache가 삭제되는 방법을 제어할 수 있습니다.
기본적으로 현재 수준 뿐만 아니라 현재 테스트에 공통된 상위 context를 공유하는 다른 모든 context hierarchy를 포함하여 전체 알고리즘을 사용하여 context cache를 지웁니다.
공통 상위 context의 하위 hierarchy에 있는 모든 ApplicationContext
instance는 context cache에서 제거되고 닫힙니다.
특정 사용 사례에 대한 전체 알고리즘이 과다한 경우 다음 예와 같이 더 간단한 현재 수준 알고리즘을 지정할 수 있습니다.
@ContextHierarchy({
@ContextConfiguration("/parent-config.xml"),
@ContextConfiguration("/child-config.xml")
})
class BaseTests {
// class body...
}
class ExtendedTests extends BaseTests {
@Test
@DirtiesContext(hierarchyMode = CURRENT_LEVEL) (1)
void test() {
// some logic that results in the child context being dirtied
}
}
1 | 현재 수준 알고리즘을 사용합니다. |
@ContextHierarchy(
ContextConfiguration("/parent-config.xml"),
ContextConfiguration("/child-config.xml"))
open class BaseTests {
// class body...
}
class ExtendedTests : BaseTests() {
@Test
@DirtiesContext(hierarchyMode = CURRENT_LEVEL) (1)
fun test() {
// some logic that results in the child context being dirtied
}
}
1 | 현재 수준 알고리즘을 사용합니다. |
EXHAUSTIVE
및 CURRENT_LEVEL
알고리즘에 대한 자세한 내용은 DirtiesContext.HierarchyMode
javadoc을 참조하세요.
@TestExecutionListeners
@TestExecutionListeners
는 TestContextManager
에 등록해야 하는 TestExecutionListener
구현을 구성하기 위한 class-level metadasta를 정의합니다.
일반적으로 @TestExecutionListeners
는 @ContextConfiguration
와 함께 사용됩니다.
다음 예제는 2개의 TestExecutionListener
구현을 등록하는 방법을 보여줍니다:
@ContextConfiguration
@TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) (1)
class CustomTestExecutionListenerTests {
// class body...
}
1 | 2개의 TestExecutionListener 구현을 등록합니다. |
@ContextConfiguration
@TestExecutionListeners(CustomTestExecutionListener::class, AnotherTestExecutionListener::class) (1)
class CustomTestExecutionListenerTests {
// class body...
}
1 | 2개의 TestExecutionListener 구현을 등록합니다. |
기본적으로 @TestExecutionListeners
는 superclass 또는 enclosing class에서 listener 상속을 지원합니다.
예제 및 추가 세부사항은 @Nested
test class configuration 및 @TestExecutionListeners
javadoc을 참조하세요.
@RecordApplicationEvents
@RecordApplicationEvents
는 Spring TestContext Framework 에 단일 테스트를 실행하는 동안 ApplicationContext
에 publish 된 모든 application event를 기록하도록 지시하는데 사용되는 class-level annotation 입니다.
기록된 event는 테스트 내에서 ApplicationEvents
API를 통해 access 할 수 있습니다.
예제 및 추가 세부사항은 Application Events 및 @RecordApplicationEvents
javadoc 을 참조하세요.
@Commit
@Commit
은 transcation test method에 대한 transaction이 test method가 완료된 후 commit 되어야 함을 나타냅니다.
@Commit`을 `@Rollback(false)
의 대체로 사용하여 코드의 의도를 좀더 명시적으로 전달할 수 있습니다.
@Rollback
과 마찬가지로 @Commit
도 class-level 또는 method-level annotation으로 선언할 수 있습니다.
다음 예는 @Commit
annotation을 사용하는 방법을 보여줍니다:
@Commit (1)
@Test
void testProcessWithoutRollback() {
// ...
}
1 | 테스트 결과를 데이터베이스에 commit 합니다. |
@Commit (1)
@Test
fun testProcessWithoutRollback() {
// ...
}
1 | 테스트 결과를 데이터베이스에 commit 합니다. |
@Rollback
@Rollback
은 test method가 완료된 후 transaction test method에 대한 transaction을 rollback 해야하는지 여부를 나타냅니다.
true
인 경우 transaction이 rollback 되며 그렇지 않으면 commit 됩니다. (@Commit
참조)
Spring TestContext Framework에서 통합 테스트를 위한 rollback은 @Rollback
이 명시적으로 선언되지 않은 경우 기본적으로 true
로 설정됩니다.
class-level annotation으로 선언되면 @Rollback
은 test class hierarchy 내의 모든 test method에 대한 기본 rolllback 체계를 정의합니다.
method-level annotation으로 선언되면 @Rollback
은 특정 test method에 대한 rollback 체계를 정의하여 잠재적으로 class-level @Rollback
또는 @Commit
체계를 재정의합니다.
다음 예제에서는 test method의 결과가 rollback되지 않도록 합니다. (즉, 결과가 데이터베이스에 commit 됨):
@Rollback(false) (1)
@Test
void testProcessWithoutRollback() {
// ...
}
1 | 결과를 rollback 하지 않습니다. |
@Rollback(false) (1)
@Test
fun testProcessWithoutRollback() {
// ...
}
1 | 결과를 rollback 하지 않습니다. |
@BeforeTransaction
@BeforeTransaction
은 Spring의 @Transactional
annotation을 사용하여 transaction 내에서 실행되도록 구성된 test method의 경우 annotation이 있는 method가 transaction이 시작되기 전에 실행되어야 함을 나타냅니다.
@BeforeTransaction
method는 public일 필요는 없으며 Java 8 기반 interface default method에 선언될 수 있습니다.
다음 예는 @BeforeTransaction
annotation을 사용하는 방법을 보여줍니다:
@BeforeTransaction (1)
void beforeTransaction() {
// logic to be run before a transaction is started
}
1 | transaction 이전에 method를 실행합니다. |
@BeforeTransaction (1)
fun beforeTransaction() {
// logic to be run before a transaction is started
}
1 | transaction 이전에 method를 실행합니다. |
@AfterTransaction
@AfterTransaction
은 Spring의 @Transactional
annotation을 사용하여 transaction 내에서 실행되도록 구성된 test method에 대한 transaction이 종료된 후 annotation이 달린 void
method를 실행해야 함을 나타냅니다.
@AfterTransaction
method는 public
일 필요는 없으며 Java 8 기반 interface default method에 선언될 수 있습니다.
@AfterTransaction (1)
void afterTransaction() {
// logic to be run after a transaction has ended
}
1 | transaction 이후에 method를 실행합니다. |
@AfterTransaction (1)
fun afterTransaction() {
// logic to be run after a transaction has ended
}
1 | transaction 이후에 method를 실행합니다. |
@Sql
@Sql
은 통합 테스트 중에 지정된 데이터베이스에 대해 실행될 SQL script를 구성하기 위해 test class 또는 test method에 annotation을 추가하는데 사용됩니다.
다음 예제는 사용방법을 보여줍니다:
@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"}) (1)
void userTest() {
// run code that relies on the test schema and test data
}
1 | 이 테스트를 위해 두 개의 script를 실행합니다. |
@Test
@Sql("/test-schema.sql", "/test-user-data.sql") (1)
fun userTest() {
// run code that relies on the test schema and test data
}
1 | 이 테스트를 위해 두 개의 script를 실행합니다. |
자세한 내용은 Executing SQL scripts declaratively with @Sql을 참조하세요.
@SqlConfig
@SqlConfig
는 @Sql
annotation으로 구성된 SQL script를 구문 분석하고 실행하는 방법을 결정하는데 사용되는 metadata를 정의합니다.
다음 예제는 사용 방법을 보여줍니다:
@Test
@Sql(
scripts = "/test-user-data.sql",
config = @SqlConfig(commentPrefix = "`", separator = "@@") (1)
)
void userTest() {
// run code that relies on the test data
}
1 | SQL script에서 comment prefix와 separator를 설정합니다. |
@Test
@Sql("/test-user-data.sql", config = SqlConfig(commentPrefix = "`", separator = "@@")) (1)
fun userTest() {
// run code that relies on the test data
}
1 | SQL script에서 comment prefix와 separator를 설정합니다. |
@SqlMergeMode
@SqlMergeMode
는 method-level @Sql
선언이 class-level @Sql
선언과 병합되는지 여부를 구성하기 위해 test class 또는 test method에 annotation을 추가하는 데 사용됩니다.
test class나 test method에서 @SqlMergeMode
가 선언되지 않으면 기본적으로 OVERRIDE
merge mode가 사용됩니다.
OVERRIDE
mode를 사용하면 method-level @Sql
선언이 class-level @Sql
선언을 효과적으로 재정의 합니다.
method-level @SqlMergeMode
선언은 class-level 선언을 재정의합니다.
다음 예제는 class level에서 @SqlMergeMode
을 사용하는 방법을 보여줍니다.
@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
@SqlMergeMode(MERGE) (1)
class UserTests {
@Test
@Sql("/user-test-data-001.sql")
void standardUserProfile() {
// run code that relies on test data set 001
}
}
1 | class의 모든 test method에 대해 @Sql merge mode를 MERGE 로 설정합니다. |
@SpringJUnitConfig(TestConfig::class)
@Sql("/test-schema.sql")
@SqlMergeMode(MERGE) (1)
class UserTests {
@Test
@Sql("/user-test-data-001.sql")
fun standardUserProfile() {
// run code that relies on test data set 001
}
}
1 | class의 모든 test method에 대해 @Sql merge mode를 MERGE 로 설정합니다. |
다음 예제는 method level에서 @SqlMergeMode
을 사용하는 방법을 보여줍니다.
@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
class UserTests {
@Test
@Sql("/user-test-data-001.sql")
@SqlMergeMode(MERGE) (1)
void standardUserProfile() {
// run code that relies on test data set 001
}
}
1 | 특정 test method에 대해 @Sql merge mode를 MERGE 로 설정합니다. |
@SpringJUnitConfig(TestConfig::class)
@Sql("/test-schema.sql")
class UserTests {
@Test
@Sql("/user-test-data-001.sql")
@SqlMergeMode(MERGE) (1)
fun standardUserProfile() {
// run code that relies on test data set 001
}
}
1 | 특정 test method에 대해 @Sql merge mode를 MERGE 로 설정합니다. |
@SqlGroup
@SqlGroup
은 여러 @Sql
annotation을 사용할 때 쓰이는 container annotation입니다.
@SqlGroup
을 사용하여 여러 개의 중첩된 @Sql
annotation을 선언하거나 Java 8의 repeatable annotation 지원과 함께 사용할 수 있으며 , 여기서 @Sql
은 동일한 class나 method에서 여러 번 선언되어 이 container annotation을 암시적으로 생성할 수 있습니다.
다음 예는 SQL group을 선언하는 방법을 보여줍니다:
@Test
@SqlGroup({ (1)
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
@Sql("/test-user-data.sql")
)}
void userTest() {
// run code that uses the test schema and test data
}
1 | SQL script group을 선언합니다. |
@Test
@SqlGroup( (1)
Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")),
Sql("/test-user-data.sql"))
fun userTest() {
// run code that uses the test schema and test data
}
1 | SQL script group을 선언합니다. |
Standard Annotation Support
다음 annotation은 Spring TestContest Framework의 모든 구성에 대한 standard semantic으로 지원됩니다. 이러한 annotation은 테스트에만 국한되지 않으며 Spring Framework 어느 곳에서나 사용할 수 있습니다.
-
@Autowired
-
@Qualifier
-
@Value
-
@Resource
(javax.annotation) JSR-250가 있는 경우 -
@ManagedBean
(javax.annotation) JSR-250가 있는 경우 -
@Inject
(javax.inject) JSR-330가 있는 경우 -
@Named
(javax.inject) JSR-330가 있는 경우 -
@PersistenceContext
(javax.persistence) JPA가 있는 경우 -
@PersistenceUnit
(javax.persistence) JPA가 있는 경우 -
@Required
-
@Transactional
(org.springframework.transaction.annotation) with 제한된 attribute 지원
JSR-250 Lifecycle Annotations
Spring TestContext Framework에서는 test class 내의 method에 |
Spring JUnit 4 Testing Annotations
다음 annotation은 SpringRunner, Spring’s JUnit 4 rules, 또는 Spring’s JUnit 4 support classes 와 함께 사용되는 경우에만 지원됩니다:
@IfProfileValue
@IfProfileValue
은 해당 annotation이 달린 테스트가 특정 테스트 환경에 대해 활성화 됨을 나타냅니다.
구성된 ProfileValueSource
가 제공된 name
에 일치하는 value
를 반환하는 경우 테스트가 활성화됩니다.
@IfProfileValue
는 class level, method level 또는 둘다 적용할 수 있습니다.
@IfProfileValue
의 class-level 사용은 해당 class 또는 subclass 내의 모든 method에 대한 method-level 사용보다 우선합니다.
특히 class level 과 method level에서 모두 활성화되면 테스트가 활성화됩니다.
@IfProfileValue
가 없으면 테스트가 암묵적으로 활성화 되었음을 의미합니다.
이것은 @Ignore
가 존재하면 항상 테스트가 비활성화 된다는 점을 제외하면 JUnit 4의 @Ignore
annotation의 의미와 유사합니다.
다음 예는 @IfProfileValue
annotation이 있는 테스트를 보여줍니다:
@IfProfileValue(name="java.vendor", value="Oracle Corporation") (1)
@Test
public void testProcessWhichRunsOnlyOnOracleJvm() {
// some logic that should run only on Java VMs from Oracle Corporation
}
1 | Java vendor가 "Oracle Corporation" 인 경우에만 테스트가 실행됩니다. |
@IfProfileValue(name="java.vendor", value="Oracle Corporation") (1)
@Test
fun testProcessWhichRunsOnlyOnOracleJvm() {
// some logic that should run only on Java VMs from Oracle Corporation
}
1 | Java vendor가 "Oracle Corporation" 인 경우에만 테스트가 실행됩니다. |
또는 values
목록 (OR
의미 포함)과 함께 @IfProfileValue
를 구성하여 JUnit 4 환경에서 test group을 TestNG 처럼 지원할 수 있습니다:
@IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"}) (1)
@Test
public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {
// some logic that should run only for unit and integration test groups
}
1 | unit tests 및 integration tests를 위해 이 테스트를 실행합니다. |
@IfProfileValue(name="test-groups", values=["unit-tests", "integration-tests"]) (1)
@Test
fun testProcessWhichRunsForUnitOrIntegrationTestGroups() {
// some logic that should run only for unit and integration test groups
}
1 | unit tests 및 integration tests를 위해 이 테스트를 실행합니다. |
@ProfileValueSourceConfiguration
@ProfileValueSourceConfiguration
은 @IfProfileValue
annotation을 통해 구성된 profile value를 검색 할 때 사용할 ProfileValueSource
의 유형을 지정하는 class-level annotation 입니다.
테스트에 대해 @ProfileValueSourceConfiguration
이 선언되지 않은 경우 기본적으로 SystemProfileValueSource
가 사용됩니다.
다음 예는 @ProfileValueSourceConfiguration
을 사용하는 방법을 보여줍니다.:
@ProfileValueSourceConfiguration(CustomProfileValueSource.class) (1)
public class CustomProfileValueSourceTests {
// class body...
}
1 | custom profile value source를 사용합니다. |
@ProfileValueSourceConfiguration(CustomProfileValueSource::class) (1)
class CustomProfileValueSourceTests {
// class body...
}
1 | custom profile value source를 사용합니다. |
@Timed
@Timed
는 해당 annotation이 달린 test method가 지정된 기간 내에(millisecond 안에) 실행을 완료해야 함을 나타냅니다.
테스트 실행 시간이 지정된 시간을 초과하면 테스트가 실패합니다.
time period에는 test method 자체 실행. rest repeat (@Repeat
참조), test fixture 설정 또는 해제가 포함됩니다.
다음 예제는 사용 방법을 보여줍니다:
@Timed(millis = 1000) (1)
public void testProcessWithOneSecondTimeout() {
// some logic that should not take longer than 1 second to run
}
1 | 테스트 기간을 1초로 설정합니다. |
@Timed(millis = 1000) (1)
fun testProcessWithOneSecondTimeout() {
// some logic that should not take longer than 1 second to run
}
1 | 테스트 기간을 1초로 설정합니다. |
Spring의 @Timed
annotation은 JUnit 4의 @Test(timeout=…)
지원과 다른 의미를 가지고 있습니다.
특히 JUnit4가 테스트 실행 시간 초과(즉, 별도의 Thread
에서 test method를 실행하여)를 처리하는 방식으로 인해 @Test(timeout=…)
는 테스트가 너무 오래 걸릴 경우 선제적으로 테스트를 실패처리합니다.
반면에 Spring의 @Timed
는 선제적으로 fail 처리를 하지 않고 test가 종료되기를 기다렸다가 fail 처리를 합니다.
@Repeat
@Repeat
는 해당 annotation이 달린 test method를 반복적으로 실행해야 함을 나타냅니다.
test method가 실행되는 횟수는 annotation에 지정됩니다.
반복되는 실행 범위에는 test method 자체의 실행과 test fixture의 설정 또는 해제가 포함됩니다.
다음 예는 @Repeat
annotation을 사용하는 방법을 보여줍니다:
@Repeat(10) (1)
@Test
public void testProcessRepeatedly() {
// ...
}
1 | 이 테스트를 10번 반복합니다. |
@Repeat(10) (1)
@Test
fun testProcessRepeatedly() {
// ...
}
1 | 이 테스트를 10번 반복합니다. |
Spring JUnit Jupiter Testing Annotations
다음 annotation은 SpringExtension
및 JUnit Jupiter(JUnit 5의 programming model)와 함께 사용될 때 지원됩니다:
@SpringJUnitConfig
@SpringJUnitConfig
는 JUnit Jupiter의 @ExtendWith(SpringExtension.class)
과 Spring TestContext Framework의 @ContextConfiguration
을 결합하여 구성된 annotation입니다.
class level에서 @ContextConfiguration
을 대체하여 사용할 수 있습니다.
@ContextConfiguration
과 @SpringJUnitConfig
사이의 유일한 차이점은 @SpringJUnitConfig
는 component class를 value
attribute로 선언할 수 있다는 것입니다.
다음 예제는 @SpringJUnitConfig
annotation을 사용하여 configuration class를 지정하는 방법을 보여줍니다:
@SpringJUnitConfig(TestConfig.class) (1)
class ConfigurationClassJUnitJupiterSpringTests {
// class body...
}
1 | configuration class를 지정합니다. |
@SpringJUnitConfig(TestConfig::class) (1)
class ConfigurationClassJUnitJupiterSpringTests {
// class body...
}
1 | configuration class를 지정합니다. |
다음 예제는 @SpringJUnitConfig
annotation을 사용하여 configuration file의 location을 지정하는 방법을 보여줍니다:
@SpringJUnitConfig(locations = "/test-config.xml") (1)
class XmlJUnitJupiterSpringTests {
// class body...
}
1 | configuration file의 location을 지정합니다. |
@SpringJUnitConfig(locations = ["/test-config.xml"]) (1)
class XmlJUnitJupiterSpringTests {
// class body...
}
1 | configuration file의 location을 지정합니다. |
자세한 내용은 Context Management, @SpringJUnitConfig
javadoc 및 `@ContextConfiguration`을 참조하세요.
@SpringJUnitWebConfig
@SpringJUnitWebConfig
는 JUnit Jupiter의 @ExtendWith(SpringExtension.class)
과 Spring TestContext Framework의 @ContextConfiguration
및 @WebAppConfiguration
을 결합하여 구성된 annotation입니다.
class level에서 @ContextConfiguration
및 @WebAppConfiguration
을 대체하여 사용할 수 있습니다.
@ContextConfiguration
과 @SpringJUnitWebConfig
사이의 유일한 차이점은 @SpringJUnitWebConfig
는 component class를 value
attribute로 선언할 수 있다는 것입니다.
또한 @WebAppConfiguration
의 value
attribute를 @SpringJUnitWebConfig
에서 resourcePath
attribute를 사용하여 재정의할 수 있습니다.
다음 예제는 @SpringJUnitWebConfig
annotation를 사용하여 configuration class를 지정하는 방법을 보여줍니다:
@SpringJUnitWebConfig(TestConfig.class) (1)
class ConfigurationClassJUnitJupiterSpringWebTests {
// class body...
}
1 | configuration class을 지정합니다. |
@SpringJUnitWebConfig(TestConfig::class) (1)
class ConfigurationClassJUnitJupiterSpringWebTests {
// class body...
}
1 | configuration class을 지정합니다. |
다음 예제는 @SpringJUnitWebConfig
annotation을 사용하여 configuration file의 location을 지정하는 방법을 보여줍니다:
@SpringJUnitWebConfig(locations = "/test-config.xml") (1)
class XmlJUnitJupiterSpringWebTests {
// class body...
}
1 | configuration file의 location을 지정합니다. |
@SpringJUnitWebConfig(locations = ["/test-config.xml"]) (1)
class XmlJUnitJupiterSpringWebTests {
// class body...
}
1 | configuration file의 location을 지정합니다. |
자세한 내용은 Context Management, @SpringJUnitWebConfig
, @ContextConfiguration
, 및 @WebAppConfiguration
의 javadoc을 참조하세요.
@TestConstructor
@TestConstructor
은 test class construct의 parameter를 테스트의 ApplicationContext
의 component로부터 autowired 되는 방식을 구성하는데 사용되는 type-level annotation 입니다.
@TestConstructor
is a type-level annotation that is used to configure how the parameters of a test class constructor are autowired from components in the test’s ApplicationContext
.
@TestConstructor
가 없거나 test class에 meta가 있는 경우 기본적으로 test constructor autowire mode 가 사용됩니다.
default mode를 변경하는 방법에 대한 자세한 내용은 아래 tip을 참조하세요.
그러나 constructor에 대한 @Autowired
의 local 선언이 @TestConstructor
와 default mode보다 우선한다는 점에 유의하세요.
Changing the default test constructor autowire mode
기본 test constructor autowire mode 는 Spring Framework 5.3부터 defaut mode는 JUnit Platform configuration parameter로 구성될 수도 있습니다. 만약 |
Spring Framework 5.2부터 @TestConstructor` 는 JUnit Jupiter과 함께 사용하기 위한 SpringExtension 과 결합만 지원됩니다.
Spring Boot Test에서 @SpringJUnitConfig 및 @SpringJUnitWebConfig 또는 다양한 test관련 annotation을 사용할 때 SpringExtension 은 자동으로 등록되는 경우가 많습니다.
|
@NestedTestConfiguration
@NestedTestConfiguration
은 inner test class를 위한 enclosing class hierarchy 에서 Spring test configuration annotation이 처리되는 방식을 구성하는데 사용되는 type-level annotaion 입니다.
@NestedTestConfiguration
가 test class, super type hierarchy 또는 enclosing class hierarchy에 없거나 meta가 포함되어 있는 경우 default enclosing configuration inheritance mode 가 사용됩니다.
default mode를 변경하는 방법에 대한 자세한 내용은 아래 tip을 참조하세요.
Changing the default enclosing configuration inheritance mode
default enclosing configuration inheritance mode 는 |
Spring TestContext Framework는 다음 annotation에 대해 @NestedTestConfiguration
semantic을 존중합니다.
@NestedTestConfiguration 의 사용은 일반적으로 JUnit Jupiter의 @Nested test classes와 함께 사용하는 경우에만 의미가 있습니다.
그러나 이 annotation을 사용하는 Spring 및 nested test class를 지원하는 다른 test framework가 있을 수 있습니다.
|
예제 및 자세한 내용은 @Nested
test class configuration을 참조하세요.
@EnabledIf
@EnabledIf
는 annotation이 달린 JUnit Jupiter test class 또는 test method가 활성화 되었음을 알리는데 사용되며 제공된 expression
이 true
로 평가되면 실행되어야 합니다.
특히 표현식이 Boolean.TRUE
또는 true
와 같은 String
(대소문자 무시)으로 평가되면 테스트가 활성화됩니다.
class level에서 적용하면 해당 class 내의 모든 test method도 기본적으로 자동으로 활성화됩니다.
표현식은 다음 중 하나일 수 있습니다:
-
Spring Expression Language (SpEL) expression. 예를 들면:
@EnabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")
-
Placeholder for a property available in the Spring
Environment
. 예를 들면:@EnabledIf("${smoke.tests.enabled}")
-
Text literal. 예를 들면:
@EnabledIf("true")
그러나 @EnabledIf("false")
는 @Disabled
와 같고 @EnabledIf("true")
는 논리적으로 의미가 없기 때문에 property placeholder의 동적 확인 결과가 아닌 text literal은 값이 zero practical value입니다. .
@EnabledIf
를 meta-annotation으로 사용하여 custom composed annotation을 만들 수 있습니다.
예를 들어 다음과 같이 custom @EnabledOnMac
annotation을 만들 수 있습니다:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@EnabledIf(
expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
reason = "Enabled on Mac OS"
)
public @interface EnabledOnMac {}
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@EnabledIf(
expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
reason = "Enabled on Mac OS"
)
annotation class EnabledOnMac {}
@DisabledIf
@DisabledIf
는 annotation이 달린 JUnit Jupiter test class 또는 test method가 비활성화 되었음을 알리는데 사용되며 제공된 expression
이 true
로 평가되면 실행되서는 안됩니다.
특히 표현식이 Boolean.TRUE
또는 true
와 같은 String
(대소문자 무시)로 평가되면 테스트가 비활성화됩니다.
class level에서 적용하면 해당 class 내의 모든 test method도 기본적으로 자동으로 비활성화됩니다.
표현식은 다음 중 하나일 수 있습니다:
-
Spring Expression Language (SpEL) expression. 예를 들면:
@DisabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")
-
Placeholder for a property available in the Spring
Environment
. 예를 들면:@DisabledIf("${smoke.tests.disabled}")
-
Text literal. 예를 들면:
@DisabledIf("true")
그러나 @DisabledIf("true")
는 @Disabled
와 같고 @DisabledIf("false")
는 논리적으로 의미가 없으므로 property placeholder의 동적 확인 결과가 아닌 텍스트 리터럴은 값이 zero practical value입니다. .
@DisabledIf
를 meta-annotation으로 사용하여 custom composed annotation을 만들 수 있습니다.
예를 들어 다음과 같이 custom @DisabledOnMac
annotation을 만들 수 있습니다:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@DisabledIf(
expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
reason = "Disabled on Mac OS"
)
public @interface DisabledOnMac {}
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@DisabledIf(
expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",
reason = "Disabled on Mac OS"
)
annotation class DisabledOnMac {}
Meta-Annotation Support for Testing
대부분의 테스트 관련 annotation을 meta-annotations으로 사용하여 custom composed annotation을 만들고 test suite에서 구성 중복을 줄일 수 있습니다.
TestContext framework와 함께 다음 각 항목을 meta-annotation으로 사용할 수 있습니다.
-
@BootstrapWith
-
@ContextConfiguration
-
@ContextHierarchy
-
@ActiveProfiles
-
@TestPropertySource
-
@DirtiesContext
-
@WebAppConfiguration
-
@TestExecutionListeners
-
@Transactional
-
@BeforeTransaction
-
@AfterTransaction
-
@Commit
-
@Rollback
-
@Sql
-
@SqlConfig
-
@SqlMergeMode
-
@SqlGroup
-
@Repeat
(JUnit 4에서만 지원됨) -
@Timed
(JUnit 4에서만 지원됨) -
@IfProfileValue
(JUnit 4에서만 지원됨) -
@ProfileValueSourceConfiguration
(JUnit 4에서만 지원됨) -
@SpringJUnitConfig
(JUnit Jupiter에서만 지원됨) -
@SpringJUnitWebConfig
(JUnit Jupiter에서만 지원됨) -
@TestConstructor
(JUnit Jupiter에서만 지원됨) -
@NestedTestConfiguration
(JUnit Jupiter에서만 지원됨) -
@EnabledIf
(JUnit Jupiter에서만 지원됨) -
@DisabledIf
(JUnit Jupiter에서만 지원됨)
다음 예를 고려하십시오:
@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class OrderRepositoryTests { }
@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class UserRepositoryTests { }
@RunWith(SpringRunner::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }
@RunWith(SpringRunner::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
JUnit 4 기반 test suite에서 이전 구성을 반복하고 있음을 발견하면 다음과 같이 Spring의 common test configuration을 중앙 집중화하는 custom composed annotation을 도입하여 중복을 줄일 수 있습니다:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
annotation class TransactionalDevTestConfig { }
그런 다음 custom @TransactionalDevTestConfig
annotation을 사용하여 다음과 같이 개별 JUnit 4 기반 test class의 구성을 단순화 할 수 있습니다:
@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class OrderRepositoryTests { }
@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class UserRepositoryTests { }
@RunWith(SpringRunner::class)
@TransactionalDevTestConfig
class OrderRepositoryTests
@RunWith(SpringRunner::class)
@TransactionalDevTestConfig
class UserRepositoryTests
JUnit Jupiter를 사용하는 테스트를 작성하면 JUnit 5의 annotation을 meta-annotation으로도 사용할 수 있으므로 코드 중복을 더욱 줄일 수 있습니다. 다음 예를 고려하십시오:
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }
@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
JUnit Jupiter 기반 test suite에서 이전 구성을 반복하고 있음을 발견하면 다음과 같이 Spring 및 JUnit Jupiter의 common test configuration을 중앙 집중화하는 custom composed annotation을 도입하여 중복을 줄일 수 있습니다:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-data-access-config.xml")
@ActiveProfiles("dev")
@Transactional
annotation class TransactionalDevTestConfig { }
그런 다음 custom @TransactionalDevTestConfig
annotation을 사용하여 다음과 같이 개별 JUnit Jupiter 기반 test class의 구성을 단순화 할 수 있습니다:
@TransactionalDevTestConfig
class OrderRepositoryTests { }
@TransactionalDevTestConfig
class UserRepositoryTests { }
@TransactionalDevTestConfig
class OrderRepositoryTests { }
@TransactionalDevTestConfig
class UserRepositoryTests { }
JUnit Jupiter는 @Test
, @RepeatedTest
, ParameterizedTest
및 다른 meta-annotation의 사용을 지원하므로 test method 수준에서 custom composed annotation을 만들 수도 있습니다.
예를 들어 JUnit Jupiter의 @Test
및 @Tag
annotation을 Spring의 @Transactional
annotation과 결합하는 composed annotation을 작성하려는 경우 다음과 같이 @TransactionalIntegrationTest
annotation을 작성할 수 있습니다:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Transactional
@Tag("integration-test") // org.junit.jupiter.api.Tag
@Test // org.junit.jupiter.api.Test
public @interface TransactionalIntegrationTest { }
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional
@Tag("integration-test") // org.junit.jupiter.api.Tag
@Test // org.junit.jupiter.api.Test
annotation class TransactionalIntegrationTest { }
그런 다음 custom @TransactionalIntegrationTest
annotation을 사용하여 다음과 같이 개별 JUnit Jupiter 기반 test method의 구성을 단순화 할 수 있습니다:
@TransactionalIntegrationTest
void saveOrder() { }
@TransactionalIntegrationTest
void deleteOrder() { }
@TransactionalIntegrationTest
fun saveOrder() { }
@TransactionalIntegrationTest
fun deleteOrder() { }
자세한 내용은 Spring Annotation Programming Model wiki page를 참조하세요.
Spring TestContext Framework
Spring TestContext Framework (org.springframework.test.context
package에 위치함)는 사용중인 test framework와 무관한 일반적인 annotation 기반 단위 및 통합 테스트 지원을 제공합니다.
TestContext framework는 또한 annotation 기반 구성을 통해 재정의 할 수 있는 합리적인 기본값을 사용하여 구성에 대한 규칙을 중요시 합니다.
일반 test infrastructure 외에도 TestContext framework는 JUnit 4, JUnit Jupiter (AKA JUnit 5), 및 TestNG에 대한 명시적인 지원을 제공합니다.
JUnit 4 및 TestNG의 경우 Spring은 abstract
support class를 제공합니다.
또한 Spring은 custom JUnit Runner
및 JUnit 4 용 custom Rules
및 소위 POJO test class를 작성할 수있는 JUnit Jupiter 용 custom Extension`을 제공합니다.
POJO test class는 `abstract
support class와 같은 특정 class hierarchy를 확장하는데 필요하지 않습니다.
다음 section에서는 TestContext framework의 내부에 대한 개요를 제공합니다. framework의 사용에만 관심이 있고 custom listener 또는 custom loader로 확장하는데 관심이 없는 경우 configuration (context management, dependency injection, transaction management), support classes 및 annotation support section으로 직접 이동하세요.
Key Abstractions
framework의 핵심은 TestContextManager
class 와 TestContext
, TestExecutionListener
및 SmartContextLoader
interfaces로 구성됩니다.
TestContextManager
는 각 test class에 대해 생성됩니다. (예: JUnit Jupiter의 단일 test class 내에서 모든 test method를 실행하는 경우)
TestContextManager
는 차례로 현재 테스트의 context를 보유하는 TestContext`를 관리합니다.
`TestContextManager
는 또한 테스트가 진행됨에 따라 TestContext
의 상태를 업데이트하고 TestExecutionListener
구현에 위임합니다.
이 구현은 dependency injection, managing transactions 등을 통해 실제 테스트 실행을 계측합니다.
SmartContextLoader
는 주어진 test class에 대한 ApplicationContext
을 로드하는 역할을 합니다.
다양한 구현에 대한 추가 정보와 예제는 javadoc 및 Spring test suite를 참조하세요.
TestContext
TestContext
는 테스트가 실행되는 context를 캡슐화하고 (사용 중인 실제 테스트 프레임 워크에 관계없이) 해당 테스트 인스턴스에 대한 context 관리 및 캐싱 지원을 제공합니다.
TestContext
는 또한 요청 된 경우 ApplicationContext`를 로드하도록 `SmartContextLoader
에 위임합니다.
TestContextManager
TestContextManager
는 Spring TestContext Framework의 주요 진입점이며 단일 TestContext
를 관리하고 잘 정의된 test 실행 지점에서 등록된 TestExecutionListener
에 이벤트 신호를 보내는 역할을 합니다:
-
특정 test framework의 “before class” 또는 “before all” method 이전
-
Test instance 이후 처리
-
특정 test framework의 “before” 또는 “before each” method 이전.
-
test method 실행 직전, test setup 후
-
test method 실행 직후 test 해체 전.
-
특정 test framework의 “after” 또는 “after each” method 이후.
-
특정 test framework의 “after class” 또는 “after all” method 이후.
TestExecutionListener
TestExecutionListener
는 listener가 등록된 `TestContextManager`에서 publish 한 테스트 실행 이벤트에 반응하기위한 API를 정의합니다.
TestExecutionListener
Configuration 를 참조하세요.
Context Loaders
ContextLoader`는 Spring TestContext Framework에서 관리하는 통합 테스트를 위한 `ApplicationContext
를 로드하기 위한 strategy interface입니다.
component classes, active bean definition profiles, test property sources, context hierarchies 및 WebApplicationContext
지원에 대한 지원을 제공하려면 이 interface 대신 SmartContextLoader
를 구현해야합니다.
SmartContextLoader
는 original minimal ContextLoader
SPI를 대체하는 ContextLoader
interface의 확장입니다.
특히 SmartContextLoader
는 resource locations, component classes 또는 context initializers를 처리하도록 선택할 수 있습니다.
또한 `SmartContextLoader`는 로드되는 context에서 active bean definition profile 및 test property sources를 설정할 수 있습니다.
Spring은 다음 구현을 제공합니다:
-
DelegatingSmartContextLoader
: 2개의 기본 loader 중 하나이며 test class에 대해 선언된 configuration 또는 default location 또는 default configuration class의 존재 여부에 따라AnnotationConfigContextLoader
,GenericXmlContextLoader
또는GenericGroovyXmlContextLoader
에 내부적으로 위임합니다. Groovy 지원은 Groovy가 classpath에 있는 경우에만 활성화됩니다. -
WebDelegatingSmartContextLoader
: 2개의 기본 loader 중 하나이며 test class에 대해 선언된 configuration 또는 default location 또는 default configuration class의 존재 여부에 따라AnnotationConfigWebContextLoader
,GenericXmlWebContextLoader
또는GenericGroovyXmlWebContextLoader
에 내부적으로 위임합니다. Groovy 지원은 Groovy가 classpath에 있는 경우에만 활성화됩니다. -
AnnotationConfigContextLoader
: component class에서 standardApplicationContext
를 로드합니다. -
AnnotationConfigWebContextLoader
: component class에서 standardWebApplicationContext
를 로드합니다. -
GenericGroovyXmlContextLoader
: Groovy script 또는 XML configuration file인 resource에서 standardApplicationContext
를 로드합니다. -
GenericGroovyXmlWebContextLoader
: Groovy script 또는 XML configuration file인 resource에서 standardWebApplicationContext
를 로드합니다. -
GenericXmlContextLoader
: XML resource location에서 standardApplicationContext
를 로드합니다. -
GenericXmlWebContextLoader
: XML resource location에서 standardWebApplicationContext
를 로드합니다.
Bootstrapping the TestContext Framework
Spring TestContext Framework의 내부에 대한 모든 기본 구성은 일반적인 사용 사례에 충분합니다.
그러나 개발 팀 또는 third party framework가 default ContextLoader
를 변경하고 custom TestContext
또는 ContextCache`를 구현하고, `ContextCustomizerFactory
및 TestExecutionListener
구현의 default set를 확장하려는 경우가 있습니다.
TestContext framework가 작동하는 방법에 대한 low-level의 제어를 위해 Spring은 bootstrapping strategy를 제공합니다.
TestContextBootstrapper
는 TestContext framework를 bootstrapping 하기위한 SPI를 정의합니다.
TestContextBootstrapper
는 TestContextManager
에서 현재 테스트에 대한 TestExecutionListener
구현을 로드하고 관리하는 TestContext
를 빌드하는데 사용됩니다.
@BootstrapWith
를 직접 사용하거나 meta-annotation을 사용하여 test class (또는 test class hierarchy)에 대한 custom bootstrapping strategy를 구성할 수 있습니다.
bootstrapper가 @BootstrapWith
를 사용하여 명시적으로 구성되지 않은 경우 @WebAppConfiguration
의 존재 여부에 따라 DefaultTestContextBootstrapper
또는 WebTestContextBootstrapper
가 사용됩니다.
TestContextBootstrapper
SPI는 향후 (새로운 요구사항을 수용하기 위해) 변경 될 가능성이 있으므로 구현자가 interface를 직접 구현하지 말고 대신 AbstractTestContextBootstrapper
또는 구체적인 subclass 중 하나를 확장하는 것이 좋습니다.
TestExecutionListener
Configuration
Spring은 기본적으로 정확히 다음 순서로 등록 된 다음 TestExecutionListener
구현을 제공합니다:
-
ServletTestExecutionListener
:WebApplicationContext
에 대한 Servlet API mock을 구성합니다. -
DirtiesContextBeforeModesTestExecutionListener
: “before” mode에 대한@DirtiesContext
annotation을 처리합니다. -
ApplicationEventsTestExecutionListener
:ApplicationEvents
에 대한 지원을 제공합니다. -
DependencyInjectionTestExecutionListener
: test instance에 대한 dependency injection을 제공합니다. -
DirtiesContextTestExecutionListener
: “after” mode에 대한@DirtiesContext
annotation을 처리합니다. -
TransactionalTestExecutionListener
: default rollback semantic과 함께 transactional test 실행을 제공합니다. -
SqlScriptsTestExecutionListener
:@Sql
annotation을 사용하여 구성된 SQL script를 실행합니다. -
EventPublishingTestExecutionListener
: 테스트의ApplicationContext
에 test execution event를 publish합니다 (Test Execution Events 참조).
Registering TestExecutionListener
Implementations
@TestExecutionListeners
annotation을 사용하여 test class 및 subclass에 대한 TestExecutionListener
구현을 등록 할 수 있습니다.
자세한 내용과 예제는 @TestExecutionListeners
에 대한 annotation 지원 및 javadoc을 참조하세요.
Automatic Discovery of Default TestExecutionListener
Implementations
@TestExecutionListeners
를 사용하여 TestExecutionListener
구현을 등록하는 것은 제한된 테스트 시나리오에서 사용되는 custom listener에 적합합니다.
그러나 custom listener를 전체 test suite에서 사용해야하는 경우 번거로울 수 있습니다.
이 문제는 SpringFactoriesLoader
메커니즘을 통한 default TestExecutionListener
구현의 자동 검색 지원을 통해 해결됩니다.
특히 spring-test
module은 META-INF/spring.factories
properties file의 org.springframework.test.context.TestExecutionListener
key 아래에 모든 핵심 default TestExecutionListener
구현을 선언합니다.
Third-party frameworks 및 개발자는 자체 META-INF/spring.factories
properties file을 통해 동일한 방식으로 자체 TestExecutionListener
구현을 default listener 목록에 제공 할 수 있습니다.
Ordering TestExecutionListener
Implementations
TestContext framework가 앞서 언급한 SpringFactoriesLoader
메커니즘을 통해 default TestExecutionListener
구현을 발견하면 인스턴스화 된 listener는 Spring의 Ordered
interface와 @Order
annotation을 준수하는 Spring의 AnnotationAwareOrderComparator
를 사용하여 정렬됩니다.
AbstractTestExecutionListener
및 Spring에서 제공하는 모든 default TestExecutionListener
구현은 적절한 값으로 Ordered
를 구현합니다.
따라서 third-party framework 및 개발자는 Ordered
를 구현하거나 @Order
를 선언하여 default TestExecutionListener
구현이 올바른 순서로 등록되었는지 확인해야합니다.
각 core listener에 할당되는 값에 대한 세부 사항은 core default TestExecutionListener
구현의 getOrder()
method에 대한 javadoc을 참조하세요.
Merging TestExecutionListener
Implementations
custom TestExecutionListener
가 @TestExecutionListeners
를 통해 등록된 경우 default listener는 등록되지 않습니다
대부분의 일반적인 테스트 시나리오에서 이는 개발자가 모든 custom listener 외에 모든 default listener를 수동으로 선언하도록 효과적으로 강제합니다.
다음 목록은 이러한 구성 스타일을 보여줍니다:
@ContextConfiguration
@TestExecutionListeners({
MyCustomTestExecutionListener.class,
ServletTestExecutionListener.class,
DirtiesContextBeforeModesTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
SqlScriptsTestExecutionListener.class
})
class MyTest {
// class body...
}
@ContextConfiguration
@TestExecutionListeners(
MyCustomTestExecutionListener::class,
ServletTestExecutionListener::class,
DirtiesContextBeforeModesTestExecutionListener::class,
DependencyInjectionTestExecutionListener::class,
DirtiesContextTestExecutionListener::class,
TransactionalTestExecutionListener::class,
SqlScriptsTestExecutionListener::class
)
class MyTest {
// class body...
}
이 접근 방식의 문제는 개발자가 기본적으로 등록된 listener를 정확히 알아야한다는 것입니다.
또한 default listener set은 release에서 release로 변경 될 수 있습니다.
예를 들어 SqlScriptsTestExecutionListener
는 Spring Framework 4.1에 도입되었고 DirtiesContextBeforeModesTestExecutionListener
는 Spring Framework 4.2에 도입되었습니다.
또한 Spring Boot 및 Spring Security와 같은 third-party framework는 앞서 언급한 <testcontext-tel-config-automatic-discovery,automatic discovery mechanism>>을 사용하여 자체 기본 TestExecutionListener
구현을 등록합니다.
모든 default listener를 인식하고 다시 선언 할 필요가 없도록 @TestExecutionListeners`의 `mergeMode
attribute를 MergeMode.MERGE_WITH_DEFAULTS
로 설정할 수 있습니다.
MERGE_WITH_DEFAULTS
는 local로 선언된 listener가 default listener와 병합되어야 함을 나타냅니다.
병합 알고리즘은 Ordering TestExecutionListener
Implementations에 설명 된대로 목록에서 중복 항목이 제거되고 병합된 listener의 result set이 AnnotationAwareOrderComparator
의 의미 체계에 따라 정렬되도록합니다.
listener가 Ordered
를 구현하거나 @Order
로 annotation이 달린 경우 기본 값과 병합되는 위치에 영향을 줄 수 있습니다.
그렇지 않으면 lcoal로 선언된 listener가 병합 될 때 default listener 목록에 추가됩니다.
예를 들어, 이전 예제의 MyCustomTestExecutionListener
class 가 order
value (예 : 500
)를 ServletTestExecutionListener
의 order (1000
이 됨)보다 작게 구성하면 MyCustomTestExecutionListener
가 기본값 목록과 자동으로 병합 될 수 있습니다.
ServletTestExecutionListener
앞에서 이전 예제를 다음으로 대체 할 수 있습니다:
@ContextConfiguration
@TestExecutionListeners(
listeners = MyCustomTestExecutionListener.class,
mergeMode = MERGE_WITH_DEFAULTS
)
class MyTest {
// class body...
}
@ContextConfiguration
@TestExecutionListeners(
listeners = [MyCustomTestExecutionListener::class],
mergeMode = MERGE_WITH_DEFAULTS
)
class MyTest {
// class body...
}
Application Events
Spring Framework 5.3.3부터 TestContext framework는 ApplicationContext
에 publish 된 application events 기록을 지원하므로 테스트 내에서 해당 이벤트에 대해 assertion을 수행 할 수 있습니다.
단일 테스트 실행 중에 publish 된 모든 이벤트는 이벤트를 java.util.Stream
으로 처리 할 수 있는 ApplicationEvents
API를 통해 사용할 수 있습니다.
테스트에서 ApplicationEvents
를 사용하려면 다음을 수행하십시오.
-
test class가
@RecordApplicationEvents
로 annotation 처리되거나 meta-annotation 처리되었는지 확인하십시오. -
ApplicationEventsTestExecutionListener
가 등록되었는지 확인하십시오. 그러나ApplicationEventsTestExecutionListener
는 기본적으로 등록되며 default listener를 포함하지 않는@TestExecutionListeners
를 통해 custom configuration이 있는 경우에만 수동으로 등록해야합니다. -
@Autowired
로ApplicationEvents
type의 field에 annotation을 달고 테스트 및 lifecycle method (예 : JUnit Jupiter의@BeforeEach
및@AfterEach
method)에서ApplicationEvents
instance를 사용합니다.-
SpringExtension for JUnit Jupiter을 사용할 때 테스트 클래스의
@Autowired
field 대신 테스트 또는 lifecycle method에서ApplicationEvents
type의 method parameter를 선언 할 수 있습니다.
-
다음 테스트 클래스는 JUnit Jupiter 및 AssertJ 용 SpringExtension
을 사용하여 Spring 관리 component에서 method를 호출하는 동안 publish 된 application event type을 주장합니다:
@SpringJUnitConfig(/* ... */)
@RecordApplicationEvents (1)
class OrderServiceTests {
@Autowired
OrderService orderService;
@Autowired
ApplicationEvents events; (2)
@Test
void submitOrder() {
// Invoke method in OrderService that publishes an event
orderService.submitOrder(new Order(/* ... */));
// Verify that an OrderSubmitted event was published
int numEvents = events.stream(OrderSubmitted.class).count(); (3)
assertThat(numEvents).isEqualTo(1);
}
}
1 | @RecordApplicationEvents 로 테스트 클래스에 주석을 답니다. |
2 | 현재 테스트에 대한 ApplicationEvents instance를 inject합니다. |
3 | ApplicationEvents API를 사용하여 publish 된 OrderSubmitted 이벤트 수를 계산합니다. |
@SpringJUnitConfig(/* ... */)
@RecordApplicationEvents (1)
class OrderServiceTests {
@Autowired
lateinit var orderService: OrderService
@Autowired
lateinit var events: ApplicationEvents (2)
@Test
fun submitOrder() {
// Invoke method in OrderService that publishes an event
orderService.submitOrder(Order(/* ... */))
// Verify that an OrderSubmitted event was published
val numEvents = events.stream(OrderSubmitted::class).count() (3)
assertThat(numEvents).isEqualTo(1)
}
}
1 | @RecordApplicationEvents 로 테스트 클래스에 주석을 답니다. |
2 | 현재 테스트에 대한 ApplicationEvents instance를 inject합니다. |
3 | ApplicationEvents API를 사용하여 publish 된 OrderSubmitted 이벤트 수를 계산합니다. |
ApplicationEvents
API에 대한 자세한 내용은 ApplicationEvents
javadoc을 참조하십시오.
Test Execution Events
Spring Framework 5.2에 도입된 EventPublishingTestExecutionListener
는 custom TestExecutionListener
구현에 대한 대안적인 접근 방식을 제공합니다.
테스트의 ApplicationContext
에 있는 component는 EventPublishingTestExecutionListener
가 publish 한 다음 이벤트를 수신 할 수 있습니다.
각 이벤트는 TestExecutionListener
API의 method에 해당합니다.
-
BeforeTestClassEvent
-
PrepareTestInstanceEvent
-
BeforeTestMethodEvent
-
BeforeTestExecutionEvent
-
AfterTestExecutionEvent
-
AfterTestMethodEvent
-
AfterTestClassEvent
이러한 이벤트는 ApplicationContext 가 이미 로드된 경우에만 publish 됩니다.
|
이러한 이벤트는 mock bean 재설정 또는 테스트 실행 추적과 같은 다양한 이유로 사용될 수 있습니다.
custom TestExecutionListener
를 구현하는 것보다 test execution event를 사용하는 한 가지 장점은 test execution event가 test ApplicationContext
에 등록된 Spring bean에 의해 사용될 수 있으며 이러한 bean은 dependency injection 및 ApplicationContext
의 다른 기능으로부터 직접 혜택을 받을 수 있다는 것입니다.
반대로 TestExecutionListener
는 ApplicationContext
의 bean이 아닙니다.
test execution event를 수신하기 위해 Spring bean은 org.springframework.context.ApplicationListener
interface를 구현하도록 선택할 수 있습니다.
또는 listener method에 @EventListener
로 annotation을 달고 위에 나열된 특정 event types 중 하나를 수신하도록 구성 할 수 있습니다 (Annotation-based Event Listeners 참조).
이 접근법의 인기로 인해 Spring은 test execution event listener의 등록을 단순화하기 위해 다음과 같은 전용 @EventListener
annotation을 제공합니다.
이러한 annotation은 org.springframework.test.context.event.annotation
package에 있습니다.
-
@BeforeTestClass
-
@PrepareTestInstance
-
@BeforeTestMethod
-
@BeforeTestExecution
-
@AfterTestExecution
-
@AfterTestMethod
-
@AfterTestClass
Exception Handling
기본적으로 test execution event listener가 event를 사용하는 동안 exception을 발생시키면 해당 exception은 사용중인 기본 testing framework(예 : JUnit 또는 TestNG)으로 전파됩니다.
예를 들어 BeforeTestMethodEvent
를 사용하여 exception이 발생하면 해당 test method가 exception으로 인해 실패합니다.
반대로 asynchronous test execution event listener 가 exception을 throw하면 exception이 기본 testing framework로 전파되지 않습니다.
asynchronous exception handling에 대한 자세한 내용은 @EventListener
에 대한 class-level javadoc을 참조하세요.
Asynchronous Listeners
특정 test execution event listener가 event를 비동기적으로 처리하도록 하려면 Spring의 일반 @Async
지원을 사용할 수 있습니다.
자세한 내용은 @EventListener
에 대한 class-level javadoc을 참조하세요.
Context Management
각 TestContext
는 담당하는 test instance에 대한 context 관리 및 캐싱 지원을 제공합니다.
test instances는 구성된 ApplicationContext
에 대한 액세스를 자동으로 받지 않습니다.
그러나 test class가 ApplicationContextAware
interface를 구현하는 경우 ApplicationContext
에 대한 참조가 test instance에 제공됩니다.
AbstractJUnit4SpringContextTests
및 AbstractTestNGSpringContextTests
는 ApplicationContextAware
를 구현하므로 ApplicationContext
에 대한 액세스를 자동으로 제공합니다.
@Autowired ApplicationContext
Java
Kotlin
마찬가지로 테스트가 web application context를 로드하도록 구성된 경우 다음과 같이 `WebApplicationContext`를 테스트에 inject 할 수 있습니다: Java
Kotlin
|
TestContext framework를 사용하는 test class는 application context를 구성하기 위해 특정 class를 확장하거나 특정 interface를 구현할 필요가 없습니다.
대신 class level에서 @ContextConfiguration
annotation을 선언하여 구성을 수행합니다.
test class가 application context resource location 또는 component class를 명시적으로 선언하지 않는 경우 구성된 ContextLoader
는 default location 또는 default configuration class에서 context를 로드하는 방법을 결정합니다.
context resource location 및 component class 외에도 application context initializer를 통해 application context를 구성 할 수도 있습니다.
다음 section에서는 XML configuration file, Groovy script, component class (일반적으로 @Configuration
class) 또는 context initializer를 사용하여 test ApplicationContext
를 구성하기 위해 Spring의 @ContextConfiguration
annotation을 사용하는 방법을 설명합니다.
또는 advanced use case를 위해 고유한 custom SmartContextLoader
를 구현하고 구성 할 수 있습니다.
Context Configuration with XML resources
XML configuration file을 사용하여 테스트 용 ApplicationContext
를 로드하려면 @ContextConfiguration
으로 test class에 annotation을 달고 XML configuration metadata의 resource location이 포함된 배열로 locations
attribute를 구성합니다.
plain 또는 relative path (예 : context.xml
)는 test class가 정의된 package에 상대적인 classpath resource 로 처리됩니다.
slash로 시작하는 path는 absolute classpath location(예 : /org/example/config.xml
)으로 처리됩니다.
resource URL을 나타내는 path (즉, classpath:
, file:
, http:
등이 prefix로 붙은 path)는 그대로 사용됩니다.
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/app-config.xml" and
// "/test-config.xml" in the root of the classpath
@ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"}) (1)
class MyTest {
// class body...
}
1 | locations attribute를 XML file 목록으로 설정합니다. |
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "/app-config.xml" and
// "/test-config.xml" in the root of the classpath
@ContextConfiguration("/app-config.xml", "/test-config.xml") (1)
class MyTest {
// class body...
}
1 | locations attribute를 XML file 목록으로 설정합니다. |
@ContextConfiguration
은 standard Java value
attribute를 통해 locations
attribute에 대한 alias를 지원합니다.
따라서 @ContextConfiguration
에서 추가 attribute를 선언할 필요가 없는 경우 locations
attribute name의 선언을 생략하고 다음 예제에 설명된 shorthand format을 사용하여 resource location을 선언할 수 있습니다:
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-config.xml"}) (1)
class MyTest {
// class body...
}
1 | location attribute를 사용하지 않고 XML file을 지정합니다. |
@ExtendWith(SpringExtension::class)
@ContextConfiguration("/app-config.xml", "/test-config.xml") (1)
class MyTest {
// class body...
}
1 | location attribute를 사용하지 않고 XML file을 지정합니다. |
@ContextConfiguration
annotation에서 locations
및 value
attribute를 모두 생략하면 TestContext framework가 default XML resource location을 감지하려고합니다.
특히 GenericXmlContextLoader
및 GenericXmlWebContextLoader
는 test class의 이름을 기반으로 default location을 감지합니다.
class name이 com.example.MyTest
인 경우 GenericXmlContextLoader
는 "classpath:com/example/MyTest-context.xml"
에서 application context를 로드합니다.
다음 예는 이를 수행하는 방법을 보여줍니다:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration (1)
class MyTest {
// class body...
}
1 | default location에서 구성 로드. |
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration (1)
class MyTest {
// class body...
}
1 | default location에서 구성 로드. |
Context Configuration with Groovy Scripts
Groovy Bean Definition DSL을 사용하는 Groovy script를 사용하여 테스트 용 ApplicationContext
를 로드하려면 @ContextConfiguration
으로 test class에 annotation을 달고 Groovy script의 resource location을 포함하는 배열로 locations
또는 value
attribute를 구성 할 수 있습니다.
Groovy script에 대한 resource lookcup semantic은 XML configuration files에 대해 설명된 것과 동일합니다.
Enabling Groovy script support
Groovy가 classpath에 있으면 Spring TestContext Framework에서 ApplicationContext 를 로드하기 위해 Groovy script를 사용하는 지원이 자동으로 활성화됩니다.
|
다음 예는 Groovy configuration file을 지정하는 방법을 보여줍니다:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/AppConfig.groovy" and
// "/TestConfig.groovy" in the root of the classpath
@ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"}) (1)
class MyTest {
// class body...
}
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "/AppConfig.groovy" and
// "/TestConfig.groovy" in the root of the classpath
@ContextConfiguration("/AppConfig.groovy", "/TestConfig.Groovy") (1)
class MyTest {
// class body...
}
1 | Groovy configuration file location 지정. |
@ContextConfiguration
annotation에서 locations
및 value
attribute를 모두 생략하면 TestContext framework는 기본 Groovy script를 감지하려고 합니다.
특히 GenericGroovyXmlContextLoader
및 GenericGroovyXmlWebContextLoader
는 test class의 이름을 기반으로 default location을 감지합니다.
class name이 com.example.MyTest
이면 Groovy context loader 는 "classpath:com/example/MyTestContext.groovy"
에서 application context를 로드합니다.
다음 예는 default 값을 사용하는 방법을 보여줍니다:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTestContext.groovy"
@ContextConfiguration (1)
class MyTest {
// class body...
}
1 | default location에서 configuration 로드. |
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTestContext.groovy"
@ContextConfiguration (1)
class MyTest {
// class body...
}
1 | default location에서 configuration 로드. |
Declaring XML configuration and Groovy scripts simultaneously
다음 목록은 통합 테스트에서 두 가지를 결합하는 방법을 보여줍니다: Java
Kotlin
|
Context Configuration with Component Classes
component class를 사용하여 테스트 용 ApplicationContext
를 로드하려면(Java-based container configuration 참조) @ContextConfiguration
으로 test class에 annotation을 달고 component class에 대한 참조를 포함하는 배열로 classes
attribute를 구성할 수 있습니다.
다음 예는이를 수행하는 방법을 보여줍니다:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from AppConfig and TestConfig
@ContextConfiguration(classes = {AppConfig.class, TestConfig.class}) (1)
class MyTest {
// class body...
}
1 | component class 지정. |
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from AppConfig and TestConfig
@ContextConfiguration(classes = [AppConfig::class, TestConfig::class]) (1)
class MyTest {
// class body...
}
1 | component class 지정. |
Component Classes
“component class” 라는 용어는 다음 중 하나를 나타낼 수 있습니다:
|
@ContextConfiguration
annotation에서 classes
attribute를 생략하면 TestContext framework는 default configuration class의 존재를 감지하려고합니다.
특히 AnnotationConfigContextLoader
및 AnnotationConfigWebContextLoader
는 @Configuration
javadoc에 지정된대로 configuration class 구현 요구 사항을 충족하는 test class의 모든 static
nested class를 감지합니다.
configuration class의 이름은 임의적입니다.
또한 test class 는 원하는 경우 둘 이상의 static
nested configuration class를 포함 할 수 있습니다.
다음 예제에서 OrderServiceTest
class는 test class에 대한 ApplicationContext
를 로드하는데 자동으로 사용되는 Config
라는 static
nested configuration class를 선언합니다:
@SpringJUnitConfig (1)
// ApplicationContext will be loaded from the
// static nested Config class
class OrderServiceTest {
@Configuration
static class Config {
// this bean will be injected into the OrderServiceTest class
@Bean
OrderService orderService() {
OrderService orderService = new OrderServiceImpl();
// set properties, etc.
return orderService;
}
}
@Autowired
OrderService orderService;
@Test
void testOrderService() {
// test the orderService
}
}
1 | nested Config class에서 configuration 로드. |
@SpringJUnitConfig (1)
// ApplicationContext will be loaded from the nested Config class
class OrderServiceTest {
@Autowired
lateinit var orderService: OrderService
@Configuration
class Config {
// this bean will be injected into the OrderServiceTest class
@Bean
fun orderService(): OrderService {
// set properties, etc.
return OrderServiceImpl()
}
}
@Test
fun testOrderService() {
// test the orderService
}
}
1 | nested Config class에서 configuration 로드. |
Mixing XML, Groovy Scripts, and Component Classes
XML configuration file, Groovy script 및 component class (일반적으로 @Configuration
class)를 혼합하여 테스트를 위한 ApplicationContext
를 구성하는 것이 바람직 할 수 있습니다.
예를 들어, production에서 XML configuration을 사용하는 경우 @Configuration
class를 사용하여 테스트를 위한 특정 Spring 관리 components를 구성할지 또는 그 반대인지 결정할 수 있습니다.
또한 일부 third-party framework(예: Spring Boot)는 다양한 유형의 resources(예 : XML configuration file, Groovy script 및 @Configuration
class)에서 동시에 ApplicationContext
를 로드하기 위한 최상의 지원을 제공합니다.
역사적으로 Spring Framework는 standard deployment에 대해 이를 지원하지 않았습니다.
결과적으로 Spring Framework가 spring-test
module에서 제공하는 대부분의 SmartContextLoader
구현은 각 test context에 대해 하나의 resource type만 지원합니다.
그러나 이것이 둘 다 사용할 수 없다는 의미는 아닙니다.
일반적인 규칙에 대한 한 가지 예외는 GenericGroovyXmlContextLoader
및 GenericGroovyXmlWebContextLoader
가 XML configuration file과 Groovy script를 동시에 지원한다는 것입니다.
또한 third-party framework는 @ContextConfiguration
을 통해 locations
과 `classes`의 선언을 모두 지원하도록 선택할 수 있으며 TestContext framework의 표준 테스트 지원을 통해 다음 옵션을 사용할 수 있습니다.
resource location(예 : XML 또는 Groovy) 및 @Configuration
class를 사용하여 테스트를 구성하려는 경우 하나를 진입점으로 선택하고 다른 하나를 포함하거나 가져와야합니다.
예를 들어 XML 또는 Groovy script에서는 component scan을 사용하거나 일반 Spring Bean으로 정의하여 @Configuration
class를 포함 할 수 있지만 @Configuration 클래스에서는 @ImportResource
를 사용하여 XML configuration file 또는 Groovy script를 가져올 수 있습니다.
이 동작은 production에서 application을 구성하는 방법과 의미 상 동일합니다:
production configuration에서 XML 또는 Groovy resource location set 또는 production ApplicationContext
가 로드되는 @Configuration
class set을 정의하지만 여전히 다른 유형의 configuration을 include 또는 import 할 수 있습니다.
Context Configuration with Context Initializers
context initializer를 사용하여 테스트에 대한 ApplicationContext
를 구성하려면 @ContextConfiguration
으로 test class에 annotation을 달고 ApplicationContextInitializer
를 구현하는 class에 대한 참조를 포함하는 배열로 initializers
attribute를 구성합니다.
선언된 컨텍스트 context initializers는 테스트를 위해 로드된 ConfigurableApplicationContext
를 초기화하는 데 사용됩니다.
선언된 각 initializer가 지원하는 구체적인 ConfigurableApplicationContext
type은 사용중인 SmartContextLoader
(일반적으로 GenericApplicationContext
)에 의해 생성된 ApplicationContext
type과 호환되어야 합니다.
또한 initializer가 호출되는 순서는 Spring의 Ordered
interface를 구현하는지 아니면 Spring의 @Order
annotation 또는 표준 @Priority
annotation으로 주석 처리되는지에 따라 다릅니다.
다음 예제는 initializer를 사용하는 방법을 보여줍니다:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from TestConfig
// and initialized by TestAppCtxInitializer
@ContextConfiguration(
classes = TestConfig.class,
initializers = TestAppCtxInitializer.class) (1)
class MyTest {
// class body...
}
1 | configuration class 및 initializer를 사용하여 configuration 지정. |
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from TestConfig
// and initialized by TestAppCtxInitializer
@ContextConfiguration(
classes = [TestConfig::class],
initializers = [TestAppCtxInitializer::class]) (1)
class MyTest {
// class body...
}
1 | configuration class 및 initializer를 사용하여 configuration 지정. |
또한 @ContextConfiguration
에서 XML configuration file, Groovy script 또는 component class의 선언을 완전히 생략하고 대신 ApplicationContextInitializer
class만 선언하여 context에 bean을 등록 할 수 있습니다.
예를 들어 XML file 또는 configuration class에서 프로그래밍 방식으로 bean definition을 로드합니다.
다음 예는 이를 수행하는 방법을 보여줍니다:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be initialized by EntireAppInitializer
// which presumably registers beans in the context
@ContextConfiguration(initializers = EntireAppInitializer.class) (1)
class MyTest {
// class body...
}
1 | initializer만 사용하여 configuration 지정. |
@ExtendWith(SpringExtension::class)
// ApplicationContext will be initialized by EntireAppInitializer
// which presumably registers beans in the context
@ContextConfiguration(initializers = [EntireAppInitializer::class]) (1)
class MyTest {
// class body...
}
1 | initializer만 사용하여 configuration 지정. |
Context Configuration Inheritance
@ContextConfiguration
은 resource locations 또는 component classe와 superclasses에 의해 선언된 context initializers를 상속해야하는지 여부를 나타내는 boolean inheritLocations
및 inheritInitializers
attribute를 지원합니다.
두 flag의 기본값은 true입니다.
이는 test class가 resource location 또는 component class 뿐만 아니라 superclasses에서 선언한 context initializer를 상속한다는 것을 의미합니다.
특히 test class의 resource location 또는 component class는 resource location 또는 superclasses에서 선언한 annotation이 있는 class 목록에 추가됩니다.
유사하게, 주어진 test class의 initializer는 test superclasses에 의해 정의된 initializer set에 추가됩니다.
따라서 subclasses에는 resource location, component class 또는 context initializer를 확장하는 옵션이 있습니다.
@ContextConfiguration
의 inheritLocations
또는 inheritInitializers
attribute가 false
로 설정되면 test class shadow에 대한 resource location 또는 component class와 context initializers가 각각 superclass에 정의된 configuration을 효과적으로 대체합니다.
Spring Framework 5.3부터 test configuration은 enclosing class에서 상속될 수도 있습니다.
자세한 내용은 @Nested test class configuration을 참조하세요.
|
XML resource location을 사용하는 다음 예제에서 ExtendedTest
에 대한 ApplicationContext
는 base-config.xml
및 extended-config.xml
에서 순서대로 로드됩니다.
따라서 extended-config.xml
에 정의된 bean은 base-config.xml
에 정의된 bean을 override(즉, replace) 할 수 있습니다.
다음 예제는 한 class가 다른 class를 확장하고 자체 configuration file과 superclass의 configuration file을 모두 사용하는 방법을 보여줍니다:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/base-config.xml"
// in the root of the classpath
@ContextConfiguration("/base-config.xml") (1)
class BaseTest {
// class body...
}
// ApplicationContext will be loaded from "/base-config.xml" and
// "/extended-config.xml" in the root of the classpath
@ContextConfiguration("/extended-config.xml") (2)
class ExtendedTest extends BaseTest {
// class body...
}
1 | superclass에 정의된 configuration file 입니다. |
2 | subclass에 정의된 configuration file 입니다. |
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "/base-config.xml"
// in the root of the classpath
@ContextConfiguration("/base-config.xml") (1)
open class BaseTest {
// class body...
}
// ApplicationContext will be loaded from "/base-config.xml" and
// "/extended-config.xml" in the root of the classpath
@ContextConfiguration("/extended-config.xml") (2)
class ExtendedTest : BaseTest() {
// class body...
}
1 | superclass에 정의된 configuration file 입니다. |
2 | subclass에 정의된 configuration file 입니다. |
마찬가지로 component class를 사용하는 다음 예제에서 ExtendedTest
에 대한 ApplicationContext
는 BaseConfig
및 ExtendedConfig
class에서 순서대로 로드됩니다.
따라서 ExtendedConfig
에 정의된 bean은 BaseConfig
에 정의된 bean을 override(즉, replace) 할 수 있습니다.
다음 예제는 한 class가 다른 class를 확장하고 자체 configuration class와 superclass의 configuration class를 모두 사용하는 방법을 보여줍니다:
// ApplicationContext will be loaded from BaseConfig
@SpringJUnitConfig(BaseConfig.class) (1)
class BaseTest {
// class body...
}
// ApplicationContext will be loaded from BaseConfig and ExtendedConfig
@SpringJUnitConfig(ExtendedConfig.class) (2)
class ExtendedTest extends BaseTest {
// class body...
}
1 | superclass에 정의된 configuration file 입니다. |
2 | subclass에 정의된 configuration file 입니다. |
// ApplicationContext will be loaded from BaseConfig
@SpringJUnitConfig(BaseConfig::class) (1)
open class BaseTest {
// class body...
}
// ApplicationContext will be loaded from BaseConfig and ExtendedConfig
@SpringJUnitConfig(ExtendedConfig::class) (2)
class ExtendedTest : BaseTest() {
// class body...
}
1 | superclass에 정의된 configuration file 입니다. |
2 | subclass에 정의된 configuration file 입니다. |
context initializer를 사용하는 다음 예제에서 ExtendedTest
용 ApplicationContext
는 BaseInitializer
및 ExtendedInitializer
를 사용하여 초기화됩니다.
그러나 initializer가 호출되는 순서는 Spring의 Ordered
interface를 구현하는지 또는 Spring의 @Order
annotation 또는 표준 @Priority
annotation으로 주석 처리되는지 여부에 따라 다릅니다.
다음 예제는 한 class가 다른 class를 확장하고 자체 initializer와 superclass의 initializer를 모두 사용하는 방법을 보여줍니다:
// ApplicationContext will be initialized by BaseInitializer
@SpringJUnitConfig(initializers = BaseInitializer.class) (1)
class BaseTest {
// class body...
}
// ApplicationContext will be initialized by BaseInitializer
// and ExtendedInitializer
@SpringJUnitConfig(initializers = ExtendedInitializer.class) (2)
class ExtendedTest extends BaseTest {
// class body...
}
1 | superclass에 정의된 initializer 입니다. |
2 | subclass에 정의된 initializer 입니다. |
// ApplicationContext will be initialized by BaseInitializer
@SpringJUnitConfig(initializers = [BaseInitializer::class]) (1)
open class BaseTest {
// class body...
}
// ApplicationContext will be initialized by BaseInitializer
// and ExtendedInitializer
@SpringJUnitConfig(initializers = [ExtendedInitializer::class]) (2)
class ExtendedTest : BaseTest() {
// class body...
}
1 | superclass에 정의된 initializer 입니다. |
2 | subclass에 정의된 initializer 입니다. |
Context Configuration with Environment Profiles
Spring Framework는 environment 및 profile (일명 "bean definition profiles") 개념에 대한 최고 수준의 지원을 제공하며 다양한 테스트 시나리오에 대해 특정 bean definition profile을 활성화하도록 통합 테스트를 구성 할 수 있습니다.
이는 @ActiveProfiles
annotation으로 test class에 annotation을 작성하고 테스트를 위해 `ApplicationContext`를 로드할 때 활성화해야하는 profile 목록을 제공하여 수행됩니다.
SmartContextLoader SPI 구현과 함께 @ActiveProfiles 를 사용할 수 있지만 이전 ContextLoader SPI 구현에서는 @ActiveProfiles 가 지원되지 않습니다.
|
XML configuration 및 @Configuration
class가 있는 두 가지 예를 고려하십시오:
<!-- 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>
@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의 root에 있는 app-config.xml
configuration file에서 로드됩니다.
app-config.xml
을 살펴보면 accountRepository
bean이 dataSource
bean에 종속되어 있음을 알 수 있습니다.
단, dataSource
는 top-level bean으로 정의되어 있지 않습니다.
대신 dataSource
는 production
profile, dev
profile 및 default
profile에 세 번 정의됩니다.
@ActiveProfiles("dev")
로 TransferServiceTest
에 annotation을 달아 Spring TestContext framework에 active profile이 {"dev"}
로 설정된 ApplicationContext
를 로드하도록 지시합니다.
결과적으로 embedded database가 생성되고 테스트 데이터로 채워지며 accountRepository
bean은 개발 DastaSource
에 대한 참조와 연결됩니다.
그것은 통합 테스트에서 우리가 원하는 것입니다.
bean을 default
profile에 할당하는 것이 때때로 유용합니다.
default profile 내의 bean은 다른 profile이 특별히 활성화되지 않은 경우에만 포함됩니다.
이를 사용하여 application의 default state에서 사용할 “fallback” bean을 정의 할 수 있습니다.
예를 들어 dev
및 production
profile에 대한 data source를 명시 적으로 제공 할 수 있지만 둘 다 활성화되지 않은 경우 in-memory data source를 기본값으로 정의할 수 있습니다.
다음 code 목록은 XML 대신 @Configuration
class를 사용하여 동일한 configuration 및 통합 테스트를 구현하는 방법을 보여줍니다:
@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()
}
}
@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
}
}
@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()
}
}
@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()
}
}
@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을 4개의 독립적인 @Configuration
class로 나누었습니다:
-
TransferServiceConfig
:@Autowired
를 사용하여 dependency injection을 통해dataSource
를 획득합니다. -
StandaloneDataConfig
: 개발자 테스트에 적합한 embedded database의dataSource
를 정의합니다 -
JndiDataConfig
: production environment의 JNDI에서 검색되는dataSource
를 정의합니다. -
DefaultDataConfig
: profile이 활성화되지 않은 경우 default embedded database에 대한dataSource
를 정의합니다.
XML-based configuration 예제와 마찬가지로 여전히 @ActiveProfiles("dev")
로 TransferServiceTest
에 annotation을 달지만 이번에는 @ContextConfiguration
annotation을 사용하여 네 가지 configuration classe를 모두 지정합니다.
test class의 본문 자체는 완전히 변경되지 않습니다.
특정 프로젝트 내의 여러 test class에서 profile single set이 사용되는 경우가 많습니다.
따라서 @ActiveProfiles
annotation의 중복 선언을 방지하기 위해 base class에서 @ActiveProfiles
를 한 번 선언할 수 있으며 subclass는 base class에서 @ActiveProfiles
configuration을 자동으로 상속합니다.
다음 예제에서 @ActiveProfiles
선언 (및 기타 annotation)이 abstract superclass 인 AbstractIntegrationTest
로 이동되었습니다:
Spring Framework 5.3부터 테스트 구성은 enclosing class에서 상속 될 수도 있습니다.
자세한 내용은 @Nested test class configuration을 참조하십시오.
|
@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 {
}
// "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
는 다음 예제와 같이 active profile의 상속을 비활성화 하는데 사용할 수있는 inheritProfiles
attribute도 지원합니다:
// "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
}
또한, 테스트 용 active profile을 선언적 방식이 아닌 프로그래밍 방식으로 해결해야하는 경우도 있습니다. 예를 들면 다음과 같습니다:
-
현재 운영 체제.
-
지속적 통합 빌드 서버에서 테스트가 실행되고 있는지 여부.
-
특정 environment variable의 존재 여부.
-
custom class-level annotation 존재 여부
-
기타 문제
프로그래밍 방식으로 active Bean definition profile을 분석하려면 custom ActiveProfilesResolver
를 구현하고 @ActiveProfiles`의 `resolver
attribute을 사용하여 등록할 수 있습니다.
자세한 정보는 해당 javadoc을 참조하세요.
다음 예제는 custom OperatingSystemActiveProfilesResolver
를 구현하고 등록하는 방법을 보여줍니다:
// "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
}
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)
}
}
Context Configuration with Test Property Sources
Spring Framework는 property sources hierarchy가 있는 environment 개념에 대한 최고 수준의 지원을 제공하며 테스트 별 property sources로 통합 테스트를 구성할 수 있습니다.
@Configuration
class에서 사용되는 @PropertySource
annotation과 달리 test class에서 @TestPropertySource
annotation을 선언하여 test properties file 또는 inlined properties에 대한 resource location을 선언할 수 있습니다.
이러한 test property source는 annotation이 있는 통합 테스트를 위해 로드된 ApplicationContext
에 대한 Environment
의 PropertySources
set에 추가됩니다.
|
Declaring Test Property Sources
@TestPropertySource
의 locations
또는 value
attribute을 사용하여 test properties file을 구성 할 수 있습니다.
기존 및 XML XML-based properties file format이 모두 지원됩니다 — 예 : "classpath:/com/example/test.properties"
또는 "file:///path/to/file.xml"
각 경로는 Spring Resource
로 해석됩니다.
일반 path(예 : "test.properties"
)는 test class가 정의된 package에 상대적인 classpath resource로 처리됩니다.
slash로 시작하는 path는 absolute classpath resource로 처리됩니다 (예 : "/org/example/test.xml"
).
URL을 참조하는 path(예 : classpath:
, file:
또는 http:
prefix가 붙은 path)는 지정된 resource protocol을 사용하여로드됩니다.
resource location wildcards (예 : */.properties
)는 허용되지 않습니다.
각 위치는 정확히 하나의 .properties
또는 .xml
resource로 평가되어야 합니다.
다음 예제에서는 test properties file을 사용합니다:
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
// class body...
}
1 | absolute path로 properties 지정. |
@ContextConfiguration
@TestPropertySource("/test.properties") (1)
class MyIntegrationTests {
// class body...
}
1 | absolute path로 properties 지정. |
다음 예제와 같이 @TestPropertySource
의 properties
attribute를 사용하여 key-value 쌍의 형태로 inline properties를 구성할 수 있습니다.
모든 key-value 쌍은 우선 순위가 가장 높은 단일 테스트 PropertySource
로 둘러싸는 Environment
에 추가됩니다.
key-value 쌍에 대해 지원되는 구문은 Java properties file의 항목에 대해 정의된 구문과 동일합니다:
-
key=value
-
key:value
-
key value
다음 예제는 2개의 inline properties를 설정합니다:
@ContextConfiguration
@TestPropertySource(properties = {"timezone = GMT", "port: 4242"}) (1)
class MyIntegrationTests {
// class body...
}
1 | key-value 구문의 두 가지 변형을 사용하여 2개의 properties를 설정합니다. |
@ContextConfiguration
@TestPropertySource(properties = ["timezone = GMT", "port: 4242"]) (1)
class MyIntegrationTests {
// class body...
}
1 | key-value 구문의 두 가지 변형을 사용하여 2개의 properties를 설정합니다. |
Spring Framework 5.2부터 또한 각각 직접 존재하는 |
Default Properties File Detection
@TestPropertySource
가 빈 annotation으로 선언 된 경우 (즉, locations
또는 properties
attributes에 대한 명시적 값이 없음) annotation을 선언한 class와 관련된 default properties file을 감지하려고 시도합니다.
예를 들어, annotation이 달린 test class가 com.example.MyTest
인 경우 해당 default properties file은 classpath:com/example/MyTest.properties
입니다.
기본값을 감지할 수 없는 경우 IllegalStateException
이 발생합니다.
Precedence
Test properties는 @PropertySource
를 사용하여 선언적으로 또는 프로그래밍 방식으로 application에서 추가한 operating system’s environment, Java system properties 또는 property sources에 정의된 properties 보다 우선 순위가 높습니다.
따라서 test properties를 사용하여 system 및 application property sources에서 로드된 properties를 선택적으로 재정의 할 수 있습니다.
또한 inline properties는 resource location에서 로드된 properties보다 우선 순위가 높습니다.
그러나 @DynamicPropertySource
를 통해 등록된 properties는 `@TestPropertySource`를 통해 로드된 properties보다 우선 순위가 높습니다.
다음 예에서 "/test.properties"
에 정의된 timezone
및 port
properties와 모든 properties는 system 및 application property sources에 정의된 동일한 이름의 properties를 재정의합니다.
또한 "/test.properties"
file이 timezone
및 port
properties에 대한 항목을 정의하면 properties
attribute를 사용하여 선언된 inline properties에 의해 재정의됩니다.
다음 예제는 file과 inline 모두에서 properties를 지정하는 방법을 보여줍니다:
@ContextConfiguration
@TestPropertySource(
locations = "/test.properties",
properties = {"timezone = GMT", "port: 4242"}
)
class MyIntegrationTests {
// class body...
}
@ContextConfiguration
@TestPropertySource("/test.properties",
properties = ["timezone = GMT", "port: 4242"]
)
class MyIntegrationTests {
// class body...
}
Inheriting and Overriding Test Property Sources
@TestPropertySource
는 superclass에서 선언한 properties file 및 inline properties의 resource location을 상속해야하는지 여부를 나타내는 boolean inheritLocations
및 inheritProperties
attribute를 지원합니다.
두 flag의 기본 값은 true
입니다.
즉, test class는 모든 superclass에서 선언한 location과 inline properties를 상속합니다.
특히, test class의 location 및 inline properties는 superclass에서 선언한 location 및 inline properties에 추가됩니다.
따라서 subclasses에는 location 및 inline properties를 확장하는 option이 있습니다.
나중에 나타나는 properties는 이전에 나타나는 동일한 이름의 properties를 shadow (즉, override)합니다.
또한 앞서 언급한 우선 순위 규칙은 상속된 test property source에도 적용됩니다.
@TestPropertySource
의 inheritLocations
또는 inheritProperties
attribute가 false로 설정되면 test class shadow에 대한 location 또는 inline properties가 각각 superclass에 정의된 configuration을 효과적으로 대체합니다.
Spring Framework 5.3부터 test configuration은 enclosing class에서 상속될 수도 있습니다.
자세한 내용은 @Nested test class configuration을 참조하십시오.
|
다음 예에서는 base.properties
file만 test property source로 사용하여 BaseTest
에 대한 ApplicationContext
를 로드합니다.
반대로 ExtendedTest
용 ApplicationContext
는 base.properties
및 extended.properties
file을 test property source location으로 사용하여 로드됩니다.
다음 예제에서는 properties
file을 사용하여 subclass와 해당 superclass 모두에서 properties을 정의하는 방법을 보여줍니다:
@TestPropertySource("base.properties")
@ContextConfiguration
class BaseTest {
// ...
}
@TestPropertySource("extended.properties")
@ContextConfiguration
class ExtendedTest extends BaseTest {
// ...
}
@TestPropertySource("base.properties")
@ContextConfiguration
open class BaseTest {
// ...
}
@TestPropertySource("extended.properties")
@ContextConfiguration
class ExtendedTest : BaseTest() {
// ...
}
다음 예제에서는 인라인 된 key1
property만 사용하여 BaseTest
에 대한 ApplicationContext
를 로드합니다.
반대로 ExtendedTest
용 ApplicationContext
는 인라인 된 key1
및 key2
properties를 사용하여 로드됩니다.
다음 예제는 inline properties를 사용하여 subclass와 해당 superclass 모두에서 properties를 정의하는 방법을 보여줍니다:
@TestPropertySource(properties = "key1 = value1")
@ContextConfiguration
class BaseTest {
// ...
}
@TestPropertySource(properties = "key2 = value2")
@ContextConfiguration
class ExtendedTest extends BaseTest {
// ...
}
@TestPropertySource(properties = ["key1 = value1"])
@ContextConfiguration
open class BaseTest {
// ...
}
@TestPropertySource(properties = ["key2 = value2"])
@ContextConfiguration
class ExtendedTest : BaseTest() {
// ...
}
Context Configuration with Dynamic Property Sources
Spring Framework 5.2.5부터 TestContext framework는 @DynamicPropertySource
annotation을 통해 dynamic properties에 대한 지원을 제공합니다.
이 annotation은 통합 테스트를 위해 로드된 ApplicationContext
에 대한 Environment
의 PropertySources
set에 dynamic value가 있는 properties를 추가해야하는 통합 테스트에서 사용할 수 있습니다.
|
class level에서 적용되는 @TestPropertySource
annotation과 달리 @DynamicPropertySource
는 Environment
에 name-value 쌍을 추가하는 데 사용되는 single DynamicPropertyRegistry
argument를 허용하는 static
method 에 적용되어야합니다.
값은 동적이며 property이 해결 될 때만 호출되는 Supplier
를 통해 제공됩니다.
일반적으로 Spring ApplicationContext
외부에서 Redis 컨테이너를 관리하기 위해 Testcontainers 프로젝트를 사용하는 다음 예제에서 볼 수 있듯이 method 참조는 값을 제공하는 데 사용됩니다.
관리되는 Redis 컨테이너의 IP address와 port는 redis.host
및 redis.port
properties를 통해 테스트의 ApplicationContext
내의 component에서 사용할 수 있습니다.
이러한 properties는 Spring의 Environment
추상화를 통해 액세스하거나 Spring 관리 components에 직접 inject 할 수 있습니다 (예 : 각각 @Value("${redis.host}")
및 @Value("${redis.port}")
).
base class에서 |
@SpringJUnitConfig(/* ... */)
@Testcontainers
class ExampleIntegrationTests {
@Container
static RedisContainer redis = new RedisContainer();
@DynamicPropertySource
static void redisProperties(DynamicPropertyRegistry registry) {
registry.add("redis.host", redis::getContainerIpAddress);
registry.add("redis.port", redis::getMappedPort);
}
// tests ...
}
@SpringJUnitConfig(/* ... */)
@Testcontainers
class ExampleIntegrationTests {
companion object {
@Container
@JvmStatic
val redis: RedisContainer = RedisContainer()
@DynamicPropertySource
@JvmStatic
fun redisProperties(registry: DynamicPropertyRegistry) {
registry.add("redis.host", redis::getContainerIpAddress)
registry.add("redis.port", redis::getMappedPort)
}
}
// tests ...
}
Precedence
dynamic properties는 @TestPropertySource
, operating system의 environment, Java system properties 또는 @PropertySource
를 사용하여 선언적으로 또는 프로그래밍 방식으로 application에서 추가한 property sources에서 로드된 properties보다 우선 순위가 높습니다.
따라서 dynamic properties을 사용하여 @TestPropertySource
, system property source 및 application property source를 통해 로드된 prooperties를 선택적으로 override 할 수 있습니다.
Loading a WebApplicationContext
TestContext framework에 표준 ApplicationContext
대신 WebApplicationContext
를 로드하도록 지시하려면 @WebAppConfiguration
을 사용하여 각 test class에 annotation을 달 수 있습니다.
test class에 @WebAppConfiguration
이 있으면 TestContext framework(TCF)에 통합 테스트를 위해 WebApplicationContext
(WAC)를 로드해야한다고 지시합니다.
background에서 TCF는 MockServletContext
가 생성되어 테스트의 WAC에 제공되는지 확인합니다.
기본적으로 MockServletContext
의 base resource path는 src/main/webapp
으로 설정됩니다.
이것은 JVM의 root에 relative path (일반적으로 project의 path)로 해석됩니다.
Maven project에서 web application의 directory 구조에 익숙하다면 src/main/webapp
이 WAR root의 default location이라는 것을 알고 있습니다.
이 기본값을 재정의해야하는 경우 @WebAppConfiguration
annotation에 대한 대체 path를 제공할 수 있습니다 (예 : @WebAppConfiguration("src/test/webapp")
).
file system 대신 classpath에서 base resource path를 참조하려면 Spring의 classpath:
prefix를 사용할 수 있습니다.
WebApplicationContext
구현에 대한 Spring의 테스트 지원은 표준 ApplicationContext
구현에 대한 지원과 동등합니다.
WebApplicationContext
로 테스트 할 때 @ContextConfiguration
을 사용하여 XML configuration file, Groovy script 또는 @Configuration
class를 자유롭게 선언할 수 있습니다.
또한 @ActiveProfiles
, @TestExecutionListeners
, @Sql
, @Rollback
등과 같은 다른 test annotation을 자유롭게 사용할 수 있습니다.
이 section의 나머지 예제는 WebApplicationContext
를 로드하기 위한 다양한 configuration option중 일부를 보여줍니다.
다음 예는 configuration에 대한 규칙에 대한 TestContext framework의 지원을 보여줍니다:
@ExtendWith(SpringExtension.class)
// defaults to "file:src/main/webapp"
@WebAppConfiguration
// detects "WacTests-context.xml" in the same package
// or static nested @Configuration classes
@ContextConfiguration
class WacTests {
//...
}
@ExtendWith(SpringExtension::class)
// defaults to "file:src/main/webapp"
@WebAppConfiguration
// detects "WacTests-context.xml" in the same package
// or static nested @Configuration classes
@ContextConfiguration
class WacTests {
//...
}
resource base path를 지정하지 않고 @WebAppConfiguration
으로 test class에 annotation을 추가하는 경우 resource path는 사실상 file:src/main/webapp
로 기본 설정됩니다.
마찬가지로 resource locations
, component classes
또는 context initializers
를 지정하지 않고 @ContextConfiguration
을 선언하면 Spring은 규칙 (즉, WacTests
class 또는 static nested @Configuration
class와 동일한 package에 있는 WacTests-context.xml
)을 사용하여 configuration의 존재를 감지하려고합니다.
다음 예제는 @WebAppConfiguration
을 사용하여 resource base path를 명시적으로 선언하고 @ContextConfiguration
을 사용하여 XML resource location을 명시적으로 선언하는 방법을 보여줍니다:
@ExtendWith(SpringExtension.class)
// file system resource
@WebAppConfiguration("webapp")
// classpath resource
@ContextConfiguration("/spring/test-servlet-config.xml")
class WacTests {
//...
}
@ExtendWith(SpringExtension::class)
// file system resource
@WebAppConfiguration("webapp")
// classpath resource
@ContextConfiguration("/spring/test-servlet-config.xml")
class WacTests {
//...
}
여기서 주목해야 할 중요한 것은 이 두 annotation이 있는 path에 대한 다른 의미 체계입니다.
기본적으로 @WebAppConfiguration
resource path는 file system 기반인 반면 @ContextConfiguration
리소스 위치는 classpath 기반입니다.
다음 예제는 Spring resource prefix를 지정하여 두 annotation에 대한 default resource 체계를 재정의할 수 있음을 보여줍니다:
@ExtendWith(SpringExtension.class)
// classpath resource
@WebAppConfiguration("classpath:test-web-resources")
// file system resource
@ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml")
class WacTests {
//...
}
@ExtendWith(SpringExtension::class)
// classpath resource
@WebAppConfiguration("classpath:test-web-resources")
// file system resource
@ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml")
class WacTests {
//...
}
이 예제의 annotation을 이전 예제와 대조하십시오
포괄적인 웹 테스트 지원을 제공하기 위해 TestContext framework에는 기본적으로 활성화되는 ServletTestExecutionListener
가 있습니다.
WebApplicationContext
에 대해 테스트 할 때 이 TestExecutionListener
는 각 test method 전에 Spring Web의 RequestContextHolder
를 사용하여 default thread-local state를 설정하고 @WebAppConfiguration
으로 구성된 base resource path를 기반으로 MockHttpServletRequest
, MockHttpServletResponse
및 ServletWebRequest
를 생성합니다.
ServletTestExecutionListener
는 또한 MockHttpServletResponse
및 ServletWebRequest
가 test instance에 inject 될 수 있는지 확인하고 테스트가 완료되면 thread-local state를 정리합니다.
테스트를 위해 WebApplicationContext`가 로드되면 web mock과 상호 작용해야 한다는 것을 알게 될 것입니다 — 예를 들어, text fixture를 설정하거나 web component를 호출한 후 assertion을 수행합니다.
다음 예는 test instance에 autowired 될 수 있는 mock을 보여줍니다.
`WebApplicationContext
및 MockServletContext
는 모두 test suite에 cache되는 반면 다른 mock은 ServletTestExecutionListener
에 의해 test method 별로 관리됩니다.
@SpringJUnitWebConfig
class WacTests {
@Autowired
WebApplicationContext wac; // cached
@Autowired
MockServletContext servletContext; // cached
@Autowired
MockHttpSession session;
@Autowired
MockHttpServletRequest request;
@Autowired
MockHttpServletResponse response;
@Autowired
ServletWebRequest webRequest;
//...
}
@SpringJUnitWebConfig
class WacTests {
@Autowired
lateinit var wac: WebApplicationContext // cached
@Autowired
lateinit var servletContext: MockServletContext // cached
@Autowired
lateinit var session: MockHttpSession
@Autowired
lateinit var request: MockHttpServletRequest
@Autowired
lateinit var response: MockHttpServletResponse
@Autowired
lateinit var webRequest: ServletWebRequest
//...
}
Context Caching
TestContext framework가 test를 위해 ApplicationContext
(또는 WebApplicationContext
)를 로드하면 해당 context는 동일한 test suite 내에서 동일한 고유 context configuration을 선언하는 모든 후속 테스트에 대해 cache되고 재사용됩니다.
caching이 작동하는 방식을 이해하려면 “unique” 및 “test suite.” 가 의미하는 바를 이해하는 것이 중요합니다.
ApplicationContext
는 로드하는 데 사용되는 configuration parameter의 조합으로 고유하게 식별 될 수 있습니다.
따라서 configuration parameter의 고유한 조합을 사용하여 context가 cache되는 key를 생성합니다.
TestContext framework는 다음 configuration parameter를 사용하여 context cache key를 빌드합니다:
-
locations
(from@ContextConfiguration
) -
classes
(from@ContextConfiguration
) -
contextInitializerClasses
(from@ContextConfiguration
) -
contextCustomizers
(fromContextCustomizerFactory
) – 여기에는@DynamicPropertySource
method와@MockBean
및@SpyBean
과 같은 Spring Boot의 테스트 지원의 다양한 기능이 포함됩니다. -
contextLoader
(from@ContextConfiguration
) -
parent
(from@ContextHierarchy
) -
activeProfiles
(from@ActiveProfiles
) -
propertySourceLocations
(from@TestPropertySource
) -
propertySourceProperties
(from@TestPropertySource
) -
resourceBasePath
(from@WebAppConfiguration
)
예를 들어 TestClassA
가 @ContextConfiguration
의 locations
(또는 value
) attribute에 대해 {"app-config.xml", "test-config.xml"}
을 지정하면 TestContext framework는 해당 ApplicationContext
를 로드하고 이를 해당 위치만을 기반으로 하는 key 아래 static
context cache에 저장합니다.
따라서 TestClassB
가 해당 location에 대해 {"app-config.xml", "test-config.xml"}
을 정의하지만 (상속을 통해 명시적으로 또는 암시적으로) @WebAppConfiguration
, 다른 ContextLoader
, 다른 active profile, 다른 context initializers, 다른 test property source 또는 다른 parent context인 경우 동일한 ApplicationContext
가 두 test class에서 공유됩니다.
즉, application context를 로드하기위한 setup cost는 test suite 당 한 번만 발생하고 후속 테스트 실행이 훨씬 빠릅니다.
Test suites and forked processes
Spring TestContext framework는 static cache에 application context를 저장합니다.
이것은 context가 말그대로 caching mechanism의 이점을 얻으려면 모든 테스트가 동일한 프로세스 또는 test suite 내에서 실행되어야합니다.
이것은 IDE 내에서 모든 테스트를 group으로 실행하여 달성할 수 있습니다.
마찬가지로 Ant, Maven 또는 Gradle과 같은 build framework로 테스트를 실행할 때 build framework가 테스트간에 분기되지 않는지 확인하는 것이 중요합니다.
예를 들어 Maven Surefire plug-in의 |
context cache의 크기는 기본 최대 크기 인 32로 제한됩니다.
최대 크기에 도달 할 때마다 최소 최근 사용(LRU : least recently used) 제거 정책이 stale context를 제거하고 닫는데 사용됩니다.
spring.test.context.cache.maxSize
라는 JVM system property를 설정하여 JVM system property 또는 build script에서 maximum size를 구성할 수 있습니다.
주어진 test suite 내에 많은 수의 application context를 로드하면 suite를 실행하는데 불필요하게 오랜 시간이 소요될 수 있으므로 로드 및 cache 된 context 수를 정확히 아는 것이 종종 도움이됩니다.
기본 context cache에 대한 통계를 보려면 org.springframework.test.context.cache
logging category에 대한 로그 수준을 DEBUG
로 설정할 수 있습니다.
테스트가 application context를 손상시키고 다시 로드해야하는 경우 (예: bean definition 또는 application object의 state 수정), @DirtiesContext
를 사용하여 test class 또는 test method에 annotation을 달 수 있습니다 (Spring Testing Annotations 의 @DirtiesContext
참조).
이것은 Spring이 동일한 application context를 요구하는 다음 테스트를 실행하기 전에 cache에서 context를 제거하고 application context를 다시 빌드하도록 지시합니다.
@DirtiesContext
annotation에 대한 지원은 기본적으로 사용되는 DirtiesContextBeforeModesTestExecutionListener
및 DirtiesContextTestExecutionListener
에서 제공됩니다.
ApplicationContext lifecycle and console logging
Spring TestContext Framework로 실행된 테스트를 디버깅해야 할 때 conole output (즉, Spring Framework 자체 또는 테스트에 대한 테스트에 대한
특정 test method 이후 JVM shutdown hook를 통해 Spring |
Context Hierarchies
로드된 Spring ApplicationContext
에 의존하는 통합 테스트를 작성할 때 종종 single context에 대해 테스트하는 것으로 충분합니다.
그러나 ApplicationContext
instance의 hierarchy에 대해 테스트하는 것이 유익하거나 필요한 경우가 있습니다.
예를 들어 Spring MVC web application을 개발하는 경우 일반적으로 Spring의 ContextLoaderListener
에 의해 로드된 root WebApplicationContext
와 Spring의 DispatcherServlet
에 의해 로드된 child WebApplicationContext가 있습니다.
그 결과 shared component 및 infrastructure configuration이 root context에서 선언되고 web-specific component에 의해 child context에서 사용되는 parent-child context hierarchy가 생성됩니다.
또 다른 사용 사례는 Spring Batch application에서 찾을 수 있습니다.
여기서 shared batch infrastructure에 대한 configuration을 제공하는 parent context와 특정 batch job의 구성에 대한 child context가 있는 경우가 많습니다.
개별 test class 또는 test class hierarchy 내에서 @ContextHierarchy
annotation으로 context configuration을 선언하여 context hierarchy를 사용하는 통합 테스트를 작성할 수 있습니다.
context hierarchy가 test class hierarchy 내의 여러 class에서 선언된 경우 context hierarchy의 특정 named level에 대한 context configuration을 merge하거나 override 할 수도 있습니다.
hierarchy에서 주어진 level에 대한 configuration을 merge 할 때 configuration resource type(즉, XML configuration file 또는 component class)은 일관성이 있어야합니다.
그렇지 않으면 서로 다른 resource type을 사용하여 구성된 context hierarchy에서 서로 다른 level을 갖는 것이 완벽하게 허용됩니다
이 section의 나머지 JUnit Jupiter 기반 예제는 hierarchy를 사용해야 하는 통합 테스트를 위한 일반적인 구성 시나리오를 보여줍니다.
ControllerIntegrationTests
는 root WebApplicationContext
(TestAppConfig
@Configuration
class를 사용하여 로드됨)와 dispatcher servlet WebApplicationContext
(WebConfig
@Configuration
class를 사용하여 로드됨)의 2가지 level로 구성된 context hierarchy를 선언하여 Spring MVC web application에 대한 일반적인 통합 테스트 시나리오를 나타냅니다.
test instance에 autowired 되는 WebApplicationContext
는 child context (즉, hierarchy에서 가장 낮은 context)에 대한 것입니다.
다음 목록은 이 configuration 시나리오를 보여줍니다:
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(classes = TestAppConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
class ControllerIntegrationTests {
@Autowired
WebApplicationContext wac;
// ...
}
@ExtendWith(SpringExtension::class)
@WebAppConfiguration
@ContextHierarchy(
ContextConfiguration(classes = [TestAppConfig::class]),
ContextConfiguration(classes = [WebConfig::class]))
class ControllerIntegrationTests {
@Autowired
lateinit var wac: WebApplicationContext
// ...
}
이 예제의 test class는 test class hierarchy 내에서 context hierarchy를 정의합니다.
AbstractWebTests
는 Spring 기반 web application에서 root WebApplicationContext
에 대한 구성을 선언합니다.
그러나 AbstractWebTests
는 @ContextHierarchy
를 선언하지 않습니다.
결과적으로 AbstractWebTests
의 subclass는 선택적으로 context hierarchy에 참여하거나 @ContextConfiguration
의 standard semantic을 따를 수 있습니다.
SoapWebServiceTests
및 RestWebServiceTests
는 모두 AbstractWebTests
를 extend 하고 @ContextHierarchy
를 사용하여 context hierarchy를 정의합니다.
결과적으로 3개의 application context가 로드되고(@ContextConfiguration
의 각 선언에 대해 하나씩) AbstractWebTests
의 conifguration에 따라 로드된 application context가 구체적인 subclass에 대해 로드된 각 context의 parent context로 설정됩니다.
다음 목록은 이 configuration 시나리오를 보여줍니다:
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
public abstract class AbstractWebTests {}
@ContextHierarchy(@ContextConfiguration("/spring/soap-ws-config.xml"))
public class SoapWebServiceTests extends AbstractWebTests {}
@ContextHierarchy(@ContextConfiguration("/spring/rest-ws-config.xml"))
public class RestWebServiceTests extends AbstractWebTests {}
@ExtendWith(SpringExtension::class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
abstract class AbstractWebTests
@ContextHierarchy(ContextConfiguration("/spring/soap-ws-config.xml"))
class SoapWebServiceTests : AbstractWebTests()
@ContextHierarchy(ContextConfiguration("/spring/rest-ws-config.xml"))
class RestWebServiceTests : AbstractWebTests()
이 예제의 class는 context hierarchy의 특정 level에 대한 configuration을 merge하기 위해 named hierarchy level의 사용을 보여줍니다
BaseTests
는 hierarchy에서 parent
와 child
의 2가지 level을 정의합니다.
ExtendedTests
는 BaseTests
를 extend하고 @ContextConfiguration
의 name
attribute에 선언된 name이 모두 child
인지 확인하여 child
hierarchy level에 대한 context configuration을 merge하도록 Spring TestContext Framework에 지시합니다.
그 결과 3개의 application context가 로드됩니다:
하나는 /app-config.xml
, 하나는 /user-config.xml
, 다른 하나는 {"/user-config.xml", "/order-config.xml"}
입니다.
이전 예제에서와 같이 /app-config.xml
에서 로드된 application context는 /user-config.xml
및 {"/user-config.xml", "/order-config.xml"}
에서 로드된 context의 parent context로 설정됩니다.
다음 목록은 이 configuration 시나리오를 보여줍니다:
@ExtendWith(SpringExtension.class)
@ContextHierarchy({
@ContextConfiguration(name = "parent", locations = "/app-config.xml"),
@ContextConfiguration(name = "child", locations = "/user-config.xml")
})
class BaseTests {}
@ContextHierarchy(
@ContextConfiguration(name = "child", locations = "/order-config.xml")
)
class ExtendedTests extends BaseTests {}
@ExtendWith(SpringExtension::class)
@ContextHierarchy(
ContextConfiguration(name = "parent", locations = ["/app-config.xml"]),
ContextConfiguration(name = "child", locations = ["/user-config.xml"]))
open class BaseTests {}
@ContextHierarchy(
ContextConfiguration(name = "child", locations = ["/order-config.xml"])
)
class ExtendedTests : BaseTests() {}
이전 예제와 달리 이 예제는 @ContextConfiguration
의 inheritLocations
flag를 false
로 설정하여 context hierarchy에서 지정된 named level의 configuration을 override하는 방법을 보여줍니다.
따라서 ExtendedTests
에 대한 application context는 /test-user-config.xml
에서만 로드되며 parent는 /app-config.xml
에서 로드된 context로 설정됩니다.
다음 목록은 이 configuration 시나리오를 보여줍니다.:
@ExtendWith(SpringExtension.class)
@ContextHierarchy({
@ContextConfiguration(name = "parent", locations = "/app-config.xml"),
@ContextConfiguration(name = "child", locations = "/user-config.xml")
})
class BaseTests {}
@ContextHierarchy(
@ContextConfiguration(
name = "child",
locations = "/test-user-config.xml",
inheritLocations = false
))
class ExtendedTests extends BaseTests {}
@ExtendWith(SpringExtension::class)
@ContextHierarchy(
ContextConfiguration(name = "parent", locations = ["/app-config.xml"]),
ContextConfiguration(name = "child", locations = ["/user-config.xml"]))
open class BaseTests {}
@ContextHierarchy(
ContextConfiguration(
name = "child",
locations = ["/test-user-config.xml"],
inheritLocations = false
))
class ExtendedTests : BaseTests() {}
Dirtying a context within a context hierarchy
context가 context hierarchy의 일부로 구성된 테스트에서 @DirtiesContext 를 사용하는 경우 hierarchyMode flag를 사용하여 context cache를 지우는 방법을 제어할 수 있습니다.
자세한 내용은 Spring Testing Annotations의 @DirtiesContext 토론 및 @DirtiesContext javadoc을 참조하세요.
|
Dependency Injection of Test Fixtures
DependencyInjectionTestExecutionListener
(기본적으로 구성됨)를 사용하면 @ContextConfiguration
또는 관련 annotation으로 구성한 application context의 bean에서 test instance의 dependency가 inject 됩니다.
선택한 annotation과 setter method 또는 field에 배치하는지 여부에 따라 setter injection, field injection 또는 둘 다 사용할 수 있습니다.
JUnit Jupiter를 사용하는 경우 선택적으로 constructor injection을 사용할 수도 있습니다 (Dependency Injection with SpringExtension
참조).
Spring의 annotation 기반 injection 지원과의 일관성을 위해 Spring의 @Autowired
annotation 또는 JSR-330의 @Inject
annotation을 field 및 setter injection에 사용할 수도 있습니다.
JUnit Jupiter 이외의 test framework의 경우 TestContext framework는 test class의 instance화에 참여하지 않습니다.
따라서 constructor에 @Autowired 또는 @Inject 를 사용하는 것은 test class에 영향을 미치지 않습니다.
|
production code에서는 field injection이 권장되지 않지만 실제로 test code에서는 field injection이 매우 자연스럽습니다.
차이점에 대한 근거는 test class를 직접 instance화하지 않는다는 것입니다.
따라서 test class에서 public constructor 또는 setter method를 호출할 필요가 없습니다.
|
@Autowired
는 type 별로 autowired를 수행하는데 사용되기 때문에 동일한 type의 bean definition이 여러 개 있는 경우 이러한 특정 bean에 대해 이 접근 방식을 사용할 수 없습니다.
이 경우 @Qualifier
와 함께 @Autowired
를 사용할 수 있습니다.
@Named
와 함께 @Inject
를 사용하도록 선택할 수도 있습니다.
또는 test class가 해당 ApplicationContext`에 access 할 수 있는 경우 (예를 들어) `applicationContext.getBean("titleRepository", TitleRepository.class)
와 같이 호출하여 명시적 조회를 수행할 수 있습니다.
test instance에 dependency injection을 적용하지 않으려면 @Autowired
또는 @Inject
로 field 또는 setter method에 annotation을 달지 마십시오.
또는 @TestExecutionListeners
로 class를 명시적으로 구성하고 listener 목록에서 DependencyInjectionTestExecutionListener.class
를 생략하여 dependency injection을 모두 비활성화할 수 있습니다.
Goals section에 설명 된대로 HibernateTitleRepository
class를 테스트하는 시나리오를 고려하십시오.
다음 두 코드 목록은 field 및 setter method에서 @Autowired
를 사용하는 방법을 보여줍니다.
application context configuration은 모든 샘플 코드 목록 뒤에 표시됩니다.
다음 코드 목록의 dependency injection 동작은 JUnit Jupiter에만 국한되지 않습니다. 지원되는 모든 test framework와 함께 동일한 DI 기술을 사용할 수 있습니다. 다음 예제는 |
첫 번째 코드 목록은 field injection에 @Autowired
를 사용하는 test class의 JUnit Jupiter 기반 구현을 보여줍니다:
@ExtendWith(SpringExtension.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {
// this instance will be dependency injected by type
@Autowired
HibernateTitleRepository titleRepository;
@Test
void findById() {
Title title = titleRepository.findById(new Long(10));
assertNotNull(title);
}
}
@ExtendWith(SpringExtension::class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {
// this instance will be dependency injected by type
@Autowired
lateinit var titleRepository: HibernateTitleRepository
@Test
fun findById() {
val title = titleRepository.findById(10)
assertNotNull(title)
}
}
또는 다음과 같이 setter injection에 @Autowired
를 사용하도록 class를 구성 할 수 있습니다.
@ExtendWith(SpringExtension.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {
// this instance will be dependency injected by type
HibernateTitleRepository titleRepository;
@Autowired
void setTitleRepository(HibernateTitleRepository titleRepository) {
this.titleRepository = titleRepository;
}
@Test
void findById() {
Title title = titleRepository.findById(new Long(10));
assertNotNull(title);
}
}
@ExtendWith(SpringExtension::class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {
// this instance will be dependency injected by type
lateinit var titleRepository: HibernateTitleRepository
@Autowired
fun setTitleRepository(titleRepository: HibernateTitleRepository) {
this.titleRepository = titleRepository
}
@Test
fun findById() {
val title = titleRepository.findById(10)
assertNotNull(title)
}
}
앞의 코드 목록은 @ContextConfiguration
annotation에서 참조하는 동일한 XML context file (즉, repository-config.xml
)을 사용합니다.
다음은 이 configuration을 보여줍니다:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- this bean will be injected into the HibernateTitleRepositoryTests class -->
<bean id="titleRepository" class="com.foo.repository.hibernate.HibernateTitleRepository">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<!-- configuration elided for brevity -->
</bean>
</beans>
setter method 중 하나에서 Java
Kotlin
지정된 qualifier value는 inject 할 특정 |
Testing Request- and Session-scoped Beans
Spring은 초기부터 Request- and session-scoped beans을 지원했으며 다음 단계에 따라 request-scoped 및 session-scoped bean을 테스트 할 수 있습니다:
-
@WebAppConfiguration
으로 test class에 annotation을 추가하여 테스트를 위해WebApplicationContext
가 로드되었는지 확인하십시오. -
mock request 또는 session을 test instance에 inject하고 test fixture를 적절하게 준비합니다.
-
구성된
WebApplicationContext
에서 검색한 web component를 invoke 합니다 (dependency injection 사용). -
mock에 대한 assertion을 수행합니다.
다음 code snippet은 로그인 사용 사례에 대한 XML configuration을 보여줍니다.
userService
bean에는 request-scope의 loginAction
bean에 대한 dependency이 있습니다.
또한 LoginAction
은 현재 HTTP 요청에서 사용자 이름과 암호를 검색하는 SpEL expressions을 사용하여 인스턴스화됩니다.
테스트에서는 TestContext framework에서 관리하는 mock을 통해 이러한 request parameter를 구성하려고합니다.
다음 목록은 이 사용 사례의 configuration을 보여줍니다:
<beans>
<bean id="userService" class="com.example.SimpleUserService"
c:loginAction-ref="loginAction"/>
<bean id="loginAction" class="com.example.LoginAction"
c:username="#{request.getParameter('user')}"
c:password="#{request.getParameter('pswd')}"
scope="request">
<aop:scoped-proxy/>
</bean>
</beans>
RequestScopedBeanTests
에서 UserService
(즉, 테스트 대상)와 MockHttpServletRequest
를 test instasnce에 inject합니다.
requestScope()
test method 내에서 제공된 MockHttpServletRequest
에서 request parameter를 설정하여 test fixture를 설정합니다.
userService
에서 loginUser()
method가 호출되면 user service가 현재 MockHttpServletRequest
(즉, 방금 parameter를 설정 한 것)에 대한 request scope의 loginAction
에 액세스 할 수 있습니다.
그런 다음 사용자 이름 및 암호에 대해 알려진 입력을 기반으로 결과에 대해 assertion을 수행 할 수 있습니다.
다음 목록은 그 방법을 보여줍니다:
@SpringJUnitWebConfig
class RequestScopedBeanTests {
@Autowired UserService userService;
@Autowired MockHttpServletRequest request;
@Test
void requestScope() {
request.setParameter("user", "enigma");
request.setParameter("pswd", "$pr!ng");
LoginResults results = userService.loginUser();
// assert results
}
}
@SpringJUnitWebConfig
class RequestScopedBeanTests {
@Autowired lateinit var userService: UserService
@Autowired lateinit var request: MockHttpServletRequest
@Test
fun requestScope() {
request.setParameter("user", "enigma")
request.setParameter("pswd", "\$pr!ng")
val results = userService.loginUser()
// assert results
}
}
다음 code snippet은 이전에 request-scope bean에 대해 본 것과 유사합니다.
그러나 이번에는 userService
bean이 session-scope userPreferences
bean에 종속되어 있습니다.
UserPreferences
bean은 현재 HTTP session에서 theme를 검색하는 SpEL expression을 사용하여 인스턴스화 됩니다.
테스트에서 TestContext framework가 관리하는 mock session에서 theme를 구성해야합니다.
다음 예는이를 수행하는 방법을 보여줍니다:
<beans>
<bean id="userService" class="com.example.SimpleUserService"
c:userPreferences-ref="userPreferences" />
<bean id="userPreferences" class="com.example.UserPreferences"
c:theme="#{session.getAttribute('theme')}"
scope="session">
<aop:scoped-proxy/>
</bean>
</beans>
SessionScopedBeanTests
에서 UserService
와 MockHttpSession
을 test instance에 inject합니다.
sessionScope()
test method 내에서 제공된 MockHttpSession
에 예상 theme attribute를 설정하여 test fixture를 설정합니다.
userService
에서 processUserPreferences()
method가 호출되면 user service가 현재 MockHttpSession
의 session-scope userPreferences
에 액세스 할 수 있으며 구성된 theme를 기반으로 결과에 대해 assertion을 수행 할 수 있습니다.
다음 예는 이를 수행하는 방법을 보여줍니다:
@SpringJUnitWebConfig
class SessionScopedBeanTests {
@Autowired UserService userService;
@Autowired MockHttpSession session;
@Test
void sessionScope() throws Exception {
session.setAttribute("theme", "blue");
Results results = userService.processUserPreferences();
// assert results
}
}
@SpringJUnitWebConfig
class SessionScopedBeanTests {
@Autowired lateinit var userService: UserService
@Autowired lateinit var session: MockHttpSession
@Test
fun sessionScope() {
session.setAttribute("theme", "blue")
val results = userService.processUserPreferences()
// assert results
}
}
Transaction Management
TestContext framework에서 transaction은 test class에서 @TestExecutionListeners
를 명시적으로 선언하지 않더라도 기본적으로 구성되는 TransactionalTestExecutionListener
에 의해 관리됩니다.
그러나 transaction 지원을 사용하려면 @ContextConfiguration
semantic으로 로드된 ApplicationContext
에서 PlatformTransactionManager
bean을 구성해야합니다 (추가 세부 사항은 나중에 제공됨).
또한 테스트를 위해 class 또는 method level에서 Spring의 @Transactional
annotation을 선언해야합니다.
Test-managed Transactions
Test-managed transaction은 TransactionalTestExecutionListener
를 사용하여 선언적으로 관리하거나 TestTransaction
(나중에 설명)을 사용하여 프로그래밍 방식으로 관리하는 transaction입니다.
이러한 transaction을 Spring-managed transaction (테스트를 위해 로드된 ApplicationContext
내에서 Spring에 의해 직접 관리되는 transaction) 또는 application-managed transactions (테스트에 의해 호출되는 application code 내에서 프로그래밍 방식으로 관리되는 transaction)과 혼동해서는 안됩니다.
Spring-managed 및 application-managed transaction은 일반적으로 test-managed transaction에 참여합니다.
그러나 Spring-managed 또는 application-managed transaction이 REQUIRED
또는 SUPPORTS
이외의 propagation type으로 구성된 경우 주의해야합니다 (자세한 내용은 transaction propagation에 대한 논의 참조).
Preemptive timeouts and test-managed transactions
Spring의 test-managed transaction과 함께 test framework에서 사전 시간 초과 형식을 사용할 경우 주의해야 합니다. 특히 Spring의 테스트 지원은 현재 test method가 호출되기 전에 transaction state를 현재 thread ( 이것이 발생할 수 있는 상황은 다음을 포함하지만 이에 국한되지 않습니다.
|
Enabling and Disabling Transactions
@Transactional`로 test method에 annotation을 달면 테스트가 기본적으로 테스트 완료 후 자동으로 rollback 되는 transaction 내에서 실행됩니다.
test class에 `@Transactional
annotation이 추가 된 경우 해당 class hierarchy 내의 각 test method는 transaction 내에서 실행됩니다.
@Transactional
로 annotation 처리되지 않은 test method (class 또는 method level)는 transaction 내에서 실행되지 않습니다.
@Transactional
은 test lifecycle method에서 지원되지 않습니다.
예를 들어 JUnit Jupiter의 @BeforeAll
, @BeforeEach
등으로 annotation이 추가된 method가 있습니다.
또한 @Transactional
로 annotation이 추가되었지만 propagation
attribute가 NOT_SUPPORTED
또는 NEVER
로 설정된 테스트는 transaction 내에서 실행되지 않습니다.
Attribute | Supported for test-managed transactions |
---|---|
|
yes |
|
only |
|
no |
|
no |
|
no |
|
no: use |
|
no: use |
Method-level lifecycle method (예 : JUnit Jupiter의 transaction 내에서 suite-level 또는 class-level lifecycle method에서 코드를 실행해야하는 경우 해당 |
AbstractTransactionalJUnit4SpringContextTests
및 AbstractTransactionalTestNGSpringContextTests
는 class level에서 transactional 지원을 위해 미리 구성되어 있습니다.
다음 예제는 Hibernate 기반 UserRepository
에 대한 통합 테스트를 작성하는 일반적인 시나리오를 보여줍니다:
@SpringJUnitConfig(TestConfig.class)
@Transactional
class HibernateUserRepositoryTests {
@Autowired
HibernateUserRepository repository;
@Autowired
SessionFactory sessionFactory;
JdbcTemplate jdbcTemplate;
@Autowired
void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Test
void createUser() {
// track initial state in test database:
final int count = countRowsInTable("user");
User user = new User(...);
repository.save(user);
// Manual flush is required to avoid false positive in test
sessionFactory.getCurrentSession().flush();
assertNumUsers(count + 1);
}
private int countRowsInTable(String tableName) {
return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
}
private void assertNumUsers(int expected) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
}
}
@SpringJUnitConfig(TestConfig::class)
@Transactional
class HibernateUserRepositoryTests {
@Autowired
lateinit var repository: HibernateUserRepository
@Autowired
lateinit var sessionFactory: SessionFactory
lateinit var jdbcTemplate: JdbcTemplate
@Autowired
fun setDataSource(dataSource: DataSource) {
this.jdbcTemplate = JdbcTemplate(dataSource)
}
@Test
fun createUser() {
// track initial state in test database:
val count = countRowsInTable("user")
val user = User()
repository.save(user)
// Manual flush is required to avoid false positive in test
sessionFactory.getCurrentSession().flush()
assertNumUsers(count + 1)
}
private fun countRowsInTable(tableName: String): Int {
return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName)
}
private fun assertNumUsers(expected: Int) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"))
}
}
Transaction Rollback and Commit Behavior에 설명된대로 데이터베이스에 대한 변경 사항은 TransactionalTestExecutionListener`에 의해 자동으로 rollback되므로 `createUser()
method가 실행된 후 데이터베이스를 정리할 필요가 없습니다.
Transaction Rollback and Commit Behavior
기본적으로 test transaction은 테스트 완료 후 자동으로 rollback됩니다.
그러나 transactional commit 및 rollback 동작은 @Commit
및 @Rollback
annotation을 통해 선언적으로 구성할 수 있습니다.
자세한 내용은 annotation support section의 해당 항목을 참조하십시오.
Programmatic Transaction Management
TestTransaction
의 static method를 사용하여 프로그래밍 방식으로 test-managed transaction과 상호 작용할 수 있습니다.
예를 들어 test method 내에서 TestTransaction
을 사용하여 before method 및 after method를 사용하여 현재 test-managed transaction을 시작 또는 종료하거나 rollback 또는 commit을 위해 현재 test-managed transaction을 구성할 수 있습니다.
TransactionalTestExecutionListener
가 활성화 될 때마다 TestTransaction
에 대한 지원이 자동으로 제공됩니다.
다음 예제는 TestTransaction
의 일부 기능을 보여줍니다.
자세한 내용은 TestTransaction
에 대한 javadoc을 참조하세요.
@ContextConfiguration(classes = TestConfig.class)
public class ProgrammaticTransactionManagementTests extends
AbstractTransactionalJUnit4SpringContextTests {
@Test
public void transactionalTest() {
// 테스트 데이터베이스에서 초기 상태를 assert합니다:
assertNumUsers(2);
deleteFromTables("user");
// 데이터베이스에 대한 변경 사항이 commit됩니다!
TestTransaction.flagForCommit();
TestTransaction.end();
assertFalse(TestTransaction.isActive());
assertNumUsers(0);
TestTransaction.start();
// 테스트가 완료된 후 자동으로 롤백 될 데이터베이스에 대해 다른 작업을 수행합니다.
}
protected void assertNumUsers(int expected) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
}
}
@ContextConfiguration(classes = [TestConfig::class])
class ProgrammaticTransactionManagementTests : AbstractTransactionalJUnit4SpringContextTests() {
@Test
fun transactionalTest() {
// 테스트 데이터베이스에서 초기 상태를 assert합니다:
assertNumUsers(2)
deleteFromTables("user")
// 데이터베이스에 대한 변경 사항이 commit됩니다!
TestTransaction.flagForCommit()
TestTransaction.end()
assertFalse(TestTransaction.isActive())
assertNumUsers(0)
TestTransaction.start()
// 테스트가 완료된 후 자동으로 롤백 될 데이터베이스에 대해 다른 작업을 수행합니다.
}
protected fun assertNumUsers(expected: Int) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"))
}
}
Running Code Outside of a Transaction
때때로 transactional test method 전후에 transactional context 외부에서 특정 코드를 실행해야 할 수 있습니다.
예를 들어 테스트를 실행하기 전에 초기 데이터베이스 상태를 확인하거나 테스트 실행 후 예상되는 transactional commit 동작을 확인합니다 (테스트가 transaction을 commit하도록 구성된 경우).
TransactionalTestExecutionListener
는 정확히 그러한 시나리오에 대해 @BeforeTransaction
및 @AfterTransaction
annotation을 지원합니다.
이러한 annotation 중 하나를 사용하여 test class의 모든 void
method 또는 test interface의 모든 void
default method에 annotation을 달 수 있으며 TransactionalTestExecutionListener
는 이전 transaction method 또는 이후 transaction method가 적절한 시간에 실행되도록 합니다.
모든 before method (예 : JUnit Jupiter의 @BeforeEach annotation이 달린 method) 및 모든 after method (예 : JUnit Jupiter의 @AfterEach annotation이 달린 method)는 transaction 내에서 실행됩니다.
또한 @BeforeTransaction 또는 @AfterTransaction annotation이 달린 method는 transaction 내에서 실행되도록 구성되지 않은 test method에 대해 실행되지 않습니다.
|
Configuring a Transaction Manager
TransactionalTestExecutionListener
는 PlatformTransactionManager
bean이 테스트를 위해 Spring ApplicationContext
에 정의될 것으로 예상합니다.
테스트의 ApplicationContext
내에 PlatformTransactionManager
의 여러 instance가 있는 경우 @Transactional("myTxMgr")
또는 @Transactional(transactionManager = "myTxMgr")
을 사용하여 qualifier를 선언하거나 @Configuration
class로 TransactionManagementConfigurer
를 구현할 수 있습니다.
테스트의 ApplicationContext
에서 transaction manager를 찾는데 사용되는 알고리즘에 대한 자세한 내용은 javadoc for TestContextTransactionUtils.retrieveTransactionManager()
에 대한 javadoc을 참조하십시오.
Demonstration of All Transaction-related Annotations
다음 JUnit Jupiter 기반 예제는 모든 transaction-related annotation을 강조하는 가상의 통합 테스트 시나리오를 표시합니다.
이 예제는 모범 사례를 보여주기위한 것이 아니라 이러한 annotation을 어떻게 사용할 수 있는지 보여주기 위한 것입니다.
추가 정보 및 구성 예는 annotation support section을 참조하십시오.
@Sql
에 대한 Transaction management에는 default transaction rollback semantic과 함께 선언적 SQL script 실행에 @Sql
을 사용하는 추가 예제가 포함되어 있습니다.
다음 예는 관련 annotations을 보여줍니다:
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {
@BeforeTransaction
void verifyInitialDatabaseState() {
// 트랜잭션이 시작되기 전에 초기 상태를 확인하는 logic
}
@BeforeEach
void setUpTestDataWithinTransaction() {
// 트랜잭션 내에서 테스트 데이터 설정
}
@Test
// class-level @Commit setting을 override 합니다.
@Rollback
void modifyDatabaseWithinTransaction() {
// 테스트 데이터를 사용하고 데이터베이스 상태를 수정하는 logic
}
@AfterEach
void tearDownWithinTransaction() {
// 트랜잭션 내에서 "해체" logic 실행
}
@AfterTransaction
void verifyFinalDatabaseState() {
// 트랜잭션이 롤백 된 후 최종 상태를 확인하는 logic
}
}
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {
@BeforeTransaction
fun verifyInitialDatabaseState() {
// 트랜잭션이 시작되기 전에 초기 상태를 확인하는 logic
}
@BeforeEach
fun setUpTestDataWithinTransaction() {
// 트랜잭션 내에서 테스트 데이터 설정
}
@Test
// class-level @Commit setting을 override 합니다.
@Rollback
fun modifyDatabaseWithinTransaction() {
// 테스트 데이터를 사용하고 데이터베이스 상태를 수정하는 logic
}
@AfterEach
fun tearDownWithinTransaction() {
// 트랜잭션 내에서 "해체" logic 실행
}
@AfterTransaction
fun verifyFinalDatabaseState() {
// 트랜잭션이 롤백 된 후 최종 상태를 확인하는 logic
}
}
Avoid false positives when testing ORM code
Hibernate session 또는 JPA persistence context의 상태를 조작하는 애플리케이션 코드를 테스트 할 때 해당 코드를 실행하는 test method 내에서 기본 작업 단위를 flush 해야합니다. 기본 작업 단위를 flush하지 못하면 오 탐지가 발생할 수 있습니다. 테스트는 통과했지만 동일한 코드가 실제 프로덕션 환경에서 예외를 발생시킵니다. 이는 in-memory 작업 단위를 유지하는 모든 ORM framework에 적용됩니다. 다음 Hibernate 기반 예제 test case에서 한 method는 오 탐지를 보여주고 다른 method는 session을 flush 한 결과를 올바르게 노출합니다: Java
Kotlin
다음 예는 JPA에 대한 matching methods를 보여줍니다: Java
Kotlin
|
Executing SQL Scripts
관계형 데이터베이스에 대한 통합 테스트를 작성할 때 SQL script를 실행하여 데이터베이스 스키마를 수정하거나 테스트 데이터를 테이블에 삽입하는 것이 유용한 경우가 많습니다.
spring-jdbc
module은 Spring ApplicationContext
가 로드될 때 SQL script를 실행하여 embedded 또는 기존 데이터베이스를 초기화 하는 지원을 제공합니다.
자세한 내용은 Embedded database support 및 Testing data access logic with an embedded database를 참조하십시오.
ApplicationContext
가 로드될 때 한 번 테스트하기 위해 데이터베이스를 초기화하는 것은 매우 유용하지만 때로는 통합 테스트 중에 데이터베이스를 수정할 수 있어야합니다.
다음 section에서는 통합 테스트 중에 SQL script를 프로그래밍 방식으로 선언적으로 실행하는 방법을 설명합니다.
Executing SQL scripts programmatically
Spring은 통합 test method 내에서 프로그래밍 방식으로 SQL script를 실행하기 위해 다음과 같은 옵션을 제공합니다.
-
org.springframework.jdbc.datasource.init.ScriptUtils
-
org.springframework.jdbc.datasource.init.ResourceDatabasePopulator
-
org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests
-
org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests
ScriptUtils
는 SQL script 작업을 위한 static utility method 모음을 제공하며 주로 framework 내에서 내부용으로 사용됩니다
그러나 SQL script를 구문 분석하고 실행하는 방법을 완전히 제어해야하는 경우 ScriptUtils
가 나중에 설명하는 다른 대안보다 더 잘 맞을 수 있습니다.
자세한 내용은 ScriptUtils
의 개별 method에 대한 javadoc을 참조하십시오.
ResourceDatabasePopulator
는 외부 resources에 정의된 SQL script를 사용하여 프로그래밍 방식으로 데이터베이스를 채우거나 초기화하거나 정리하기위한 object-based API를 제공합니다.
ResourceDatabasePopulator
는 script를 구문 분석하고 실행할 때 사용되는 character encoding, statement separator, comment delimiters, 및 error handling flag를 구성하기 위한 옵션을 제공합니다.
각 구성 옵션에는 적절한 기본값이 있습니다.
기본값에 대한 자세한 내용은 javadoc을 참조하십시오.
ResourceDatabasePopulator
에 구성된 script를 실행하려면 populate(Connection)
method를 호출하여 java.sql.Connection
에 대해 populator를 실행하거나 execute(DataSource)
method를 호출하여 javax.sql.DataSource
에 대해 populator를 실행할 수 있습니다.
다음 예제에서는 test schema 및 test data에 대한 SQL script를 지정하고 statement separator를 @@
로 설정하고 DataSource
에 대해 script를 실행합니다:
@Test
void databaseTest() {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScripts(
new ClassPathResource("test-schema.sql"),
new ClassPathResource("test-data.sql"));
populator.setSeparator("@@");
populator.execute(this.dataSource);
// run code that uses the test schema and data
}
@Test
fun databaseTest() {
val populator = ResourceDatabasePopulator()
populator.addScripts(
ClassPathResource("test-schema.sql"),
ClassPathResource("test-data.sql"))
populator.setSeparator("@@")
populator.execute(dataSource)
// run code that uses the test schema and data
}
ResourceDatabasePopulator`는 SQL script를 구문 분석하고 실행하기 위해 내부적으로 `ScriptUtils
에 위임합니다.
마찬가지로 AbstractTransactionalJUnit4SpringContextTests
및 AbstractTransactionalTestNGSpringContextTests
의 executeSqlScript(..)
method는 내부적으로 ResourceDatabasePopulator
를 사용하여 SQL script를 실행합니다.
자세한 내용은 다양한 executeSqlScript(..)
method에 대한 Javadoc을 참조하십시오.
Executing SQL scripts declaratively with @Sql
SQL script를 프로그래밍 방식으로 실행하기 위한 앞서 언급한 메커니즘 외에도 Spring TestContext Framework에서 SQL script를 선언적으로 구성할 수 있습니다.
특히 test class 또는 test method에서 @Sql
annotation을 선언하여 통합 테스트 method 전후에 주어진 데이터베이스에 대해 실행해야하는 SQL script에 대한 개별 SQL statement 또는 resource path를 구성 할 수 있습니다.
@Sql
에 대한 지원은 기본적으로 활성화되어있는 SqlScriptsTestExecutionListener
에서 제공합니다.
method-level @Sql 선언은 기본적으로 class-level 선언을 재정의합니다.
그러나 Spring Framework 5.2부터 이 동작은 @SqlMergeMode 를 통해 test class 또는 test method 별로 구성할 수 있습니다.
자세한 내용은 Merging and Overriding Configuration with @SqlMergeMode 를 참조하세요.
|
Path Resource Semantics
각 path는 Spring Resource
로 해석됩니다.
일반 path(예: "schema.sql"
)는 test class가 정의된 package에 상대적인 classpath resource로 처리됩니다.
slash로 시작하는 path는 absolute classpath resource로 처리됩니다 (예 : "/org/example/schema.sql"
).
URL을 참조하는 path (예: classpath:
, file:
, http:
prefix가 붙은 path)는 지정된 resource protocol을 사용하여 로드됩니다.
다음 예제는 JUnit Jupiter 기반 통합 테스트 클래스 내에서 class level 및 method level에서 @Sql
을 사용하는 방법을 보여줍니다:
@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {
@Test
void emptySchemaTest() {
// run code that uses the test schema without any test data
}
@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"})
void userTest() {
// run code that uses the test schema and test data
}
}
@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {
@Test
fun emptySchemaTest() {
// run code that uses the test schema without any test data
}
@Test
@Sql("/test-schema.sql", "/test-user-data.sql")
fun userTest() {
// run code that uses the test schema and test data
}
}
Default Script Detection
SQL script 나 statement가 지정되지 않은 경우 @Sql
이 선언된 위치에 따라 default
script를 검색하려고 시도합니다.
기본값을 감지할 수 없는 경우 IllegalStateException
이 발생합니다.
-
class-level 선언 : annotation이 달린 test class가
com.example.MyTest
인 경우 해당 default script는classpath:com/example/MyTest.sql
입니다. -
method-level 선언: annotation이 달린 test method의 이름이
testMethod()
이고com.example.MyTest
class에 정의된 경우 해당 default script는classpath:com/example/MyTest.testMethod.sql
입니다.
Declaring Multiple @Sql
Sets
주어진 test class 또는 test method에 대해 여러 SQL script set을 구성해야 하지만 다른 syntax configuration, 다른 error handling rule 또는 다른 execution phases per set를 사용하는 경우 @Sql
의 여러 instance를 선언 할 수 있습니다.
Java 8에서는 @Sql
을 repeatable annotation으로 사용할 수 있습니다.
그렇지 않으면 @Sql
의 여러 instance를 선언하기 위한 명시적 container로 @SqlGroup
annotation을 사용할 수 있습니다.
다음 예제는 Java 8에서 @Sql
을 repeatable annotation으로 사용하는 방법을 보여줍니다:
@Test
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`"))
@Sql("/test-user-data.sql")
void userTest() {
// run code that uses the test schema and test data
}
// Repeatable annotations with non-SOURCE retention are not yet supported by Kotlin
앞의 예제에 제시된 시나리오에서 test-schema.sql
script는 single-line comments에 대해 다른 구문을 사용합니다.
다음 예제는 @Sql
선언이 @SqlGroup
내에서 함께 그룹화된다는 점을 제외하면 앞의 예제와 동일합니다.
Java 8 이상에서 @SqlGroup
사용은 선택 사항이지만 Kotlin과 같은 다른 JVM 언어와의 호환성을 위해 @SqlGroup
을 사용해야할 수도 있습니다.
@Test
@SqlGroup({
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
@Sql("/test-user-data.sql")
)}
void userTest() {
// run code that uses the test schema and test data
}
@Test
@SqlGroup(
Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")),
Sql("/test-user-data.sql"))
fun userTest() {
// Run code that uses the test schema and test data
}
Script Execution Phases
기본적으로 SQL script는 해당 test method 전에 실행됩니다.
그러나 test method 후에 특정 script set을 실행해야하는 경우(예: 데이터베이스 상태 정리) 다음 예제와 같이 @Sql
에서 executionPhase
attribute를 사용할 수 있습니다:
@Test
@Sql(
scripts = "create-test-data.sql",
config = @SqlConfig(transactionMode = ISOLATED)
)
@Sql(
scripts = "delete-test-data.sql",
config = @SqlConfig(transactionMode = ISOLATED),
executionPhase = AFTER_TEST_METHOD
)
void userTest() {
// run code that needs the test data to be committed
// to the database outside of the test's transaction
}
@Test
@SqlGroup(
Sql("create-test-data.sql",
config = SqlConfig(transactionMode = ISOLATED)),
Sql("delete-test-data.sql",
config = SqlConfig(transactionMode = ISOLATED),
executionPhase = AFTER_TEST_METHOD))
fun userTest() {
// run code that needs the test data to be committed
// to the database outside of the test's transaction
}
ISOLATED
및 AFTER_TEST_METHOD
는 각각 Sql.TransactionMode
및 Sql.ExecutionPhase
에서 정적으로 가져옵니다.
Script Configuration with @SqlConfig
@SqlConfig
annotation을 사용하여 script parsing 및 error handling을 구성할 수 있습니다.
통합 테스트 클래스에서 class-level annotation으로 선언 된 경우 @SqlConfig
는 test class hierarchy 내의 모든 SQL script에 대한 global configuration 역할을 합니다.
@Sql
annotation의 config
attribute을 사용하여 직접 선언할 때 @SqlConfig
는 둘러싸는 @Sql
annotation 내에서 선언된 SQL script에 대한 local configuration 역할을 합니다.
@SqlConfig
의 모든 attribute에는 해당 attribute의 javadoc에 문서화 된 암시적 기본값이 있습니다
Java Language Specification의 annotation attribute에 대해 정의된 rule로 인해 안타깝게도 annotation attribute에 null
값을 할당할 수 없습니다.
따라서 상속된 global configuration의 override를 지원하기 위해 @SqlConfig
attribute에는 ""
(문자열의 경우), {}
(배열의 경우) 또는 DEFAULT
(열거형의 경우)의 명시적 기본값이 있습니다.
이 접근 방식을 사용하면 @SqlConfig
의 로컬 선언이 ""
, {}
또는 DEFAULT
이외의 값을 제공하여 @SqlConfig
의 전역 선언에서 개별 attribute을 선택적으로 override 할 수 있습니다.
global @SqlConfig
attribute은 로컬 @SqlConfig
attribute가 ""
, {}
또는 DEFAULT
이외의 명시적 값을 제공하지 않을 때마다 상속됩니다.
따라서 명시적 local configuration이 global configuration보다 우선합니다.
@Sql
및 @SqlConfig
에서 제공하는 configuration option은 ScriptUtils
및 ResourceDatabasePopulator
에서 지원하는 option과 동일하지만 <jdbc:initialize-database/>
XML namespace element에서 제공하는 option의 superset입니다.
자세한 내용은 @Sql
및 @SqlConfig
의 individual attributes에 대한 javadoc을 참조하십시오.
Transaction management for @Sql
기본적으로 SqlScriptsTestExecutionListener
는 @Sql
을 사용하여 구성된 script에 대해 원하는 transaction semantic을 유추합니다.
특히 SQL script는 @SqlConfig
의 transactionMode
attribute에 구성된 값과 test의 ApplicationContext
의 PlatformTransactionManager
존재 여부에 따라 transaction 없이 기존 Spring-managed transaction (예: @Transactional
로 annotation이 달린 테스트를 위해 TransactionalTestExecutionListener
가 관리하는 transaction) 내에서 또는 isolated transaction 내에서 실행됩니다.
그러나 최소한 javax.sql.DataSource
는 테스트의 ApplicationContext
에 있어야합니다.
SqlScriptsTestExecutionListener
에서 DataSource
및 PlatformTransactionManager
를 감지하고 transaction semantic을 유추하는데 사용하는 알고리즘이 사용자의 요구에 맞지않는 경우 @SqlConfig
의 dataSource
및 transactionManager
attribute를 설정하여 명시적인 name을 지정할 수 있습니다.
또한 @SqlConfig
의 transactionMode
attribute를 설정하여 transaction propagation 동작을 제어할 수 있습니다 (예: script가 isolated transaction에서 실행되어야 하는지 여부).
@Sql
을 사용하여 transaction management를 위해 지원되는 모든 option에 대한 철저한 논의는 이 참조 매뉴얼의 범위를 벗어나지만 @SqlConfig
및 SqlScriptsTestExecutionListener
에 대한 javadoc은 자세한 정보를 제공하며 다음 예제는 @sql
을 사용하여 JUnit Jupiter 및 transactional test를 사용하는 일반적인 테스트 시나리오를 보여줍니다:
@SpringJUnitConfig(TestDatabaseConfig.class)
@Transactional
class TransactionalSqlScriptsTests {
final JdbcTemplate jdbcTemplate;
@Autowired
TransactionalSqlScriptsTests(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Test
@Sql("/test-data.sql")
void usersTest() {
// verify state in test database:
assertNumUsers(2);
// run code that uses the test data...
}
int countRowsInTable(String tableName) {
return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
}
void assertNumUsers(int expected) {
assertEquals(expected, countRowsInTable("user"),
"Number of rows in the [user] table.");
}
}
@SpringJUnitConfig(TestDatabaseConfig::class)
@Transactional
class TransactionalSqlScriptsTests @Autowired constructor(dataSource: DataSource) {
val jdbcTemplate: JdbcTemplate = JdbcTemplate(dataSource)
@Test
@Sql("/test-data.sql")
fun usersTest() {
// verify state in test database:
assertNumUsers(2)
// run code that uses the test data...
}
fun countRowsInTable(tableName: String): Int {
return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName)
}
fun assertNumUsers(expected: Int) {
assertEquals(expected, countRowsInTable("user"),
"Number of rows in the [user] table.")
}
}
usersTest()
method가 실행된 후 데이터베이스를 정리할 필요가 없습니다.
(test method 내에서 또는 /test-data.sql
script 내에서) 데이터베이스에 대한 변경 사항은 TransactionalTestExecutionListener
에 의해 자동으로 롤백되기 때문입니다.
(자세한 내용은 transaction management 참조).
Merging and Overriding Configuration with @SqlMergeMode
Spring Framework 5.2부터는 method-level의 @Sql
선언을 class-level 선언과 merge 할 수 있습니다.
예를 들어 이를 통해 test class 당 한 번씩 database schema 또는 일부 common test data에 대한 구성을 제공한 다음 test method 별로 추가 use case별 test data를 제공할 수 있습니다.
@Sql
merging을 활성화하려면 @SqlMergeMode(MERGE)
를 사용하여 test class 또는 test method에 annotation을 추가합니다.
특정 test method (또는 특정 tes subclass)에 대한 merging을 비활성화하려면 @SqlMergeMode(OVERRIDE)
를 통해 default mode로 다시 전환할 수 있습니다.
예제 및 추가 세부 사항은 @SqlMergeMode
annotation documentation section을 참조하세요.
Parallel Test Execution
Spring Framework 5.0은 Spring TestContext Framework를 사용할 때 단일 JVM 내에서 병렬로 테스트를 실행하기위한 기본 지원을 도입했습니다. 일반적으로 이는 테스트 코드나 구성을 변경하지 않고도 대부분의 test class 또는 test method를 병렬로 실행할 수 있음을 의미합니다.
병렬 테스트 실행을 설정하는 방법에 대한 자세한 내용은 test framework, build tool 또는 IDE에 대한 설명서를 참조하세요. |
test suite에 동시성을 도입하면 예기치 않은 부작용, 이상한 런타임 동작 및 간헐적으로 또는 겉보기에 무작위로 실패하는 테스트가 발생할 수 있습니다. 따라서 Spring 팀은 테스트를 병렬로 실행하지 않을 때 다음과 같은 일반적인 지침을 제공합니다.
다음과 같은 경우 테스트를 병렬로 실행하지 마십시오.:
-
Spring Framework의
@DirtiesContext
지원을 사용할 때. -
Spring Boot의
@MockBean
또는@SpyBean
지원을 사용할 때. -
JUnit 4의
@FixMethodOrder
지원 또는 test method가 특정 순서로 실행되도록 설계된 test framework 기능을 사용할 때. 그러나 전체 test class가 병렬로 실행되는 경우에는 적용되지 않습니다. -
database, message broker, filesystem 등과 같은 공유 서비스 또는 시스템의 상태를 변경할 때. 이는 embbed 및 external system 모두에 적용됩니다.
현재 테스트에 대한 이는 |
Spring TestContext Framework의 병렬 테스트 실행은 TestContext 에 대한 javadoc에 설명된대로 기본 TestContext 구현이 copy constructor를 제공하는 경우에만 가능합니다.
Spring에서 사용되는 DefaultTestContext 는 이러한 constructor를 제공합니다.
그러나 custom TestContext 구현을 제공하는 third-party library를 사용하는 경우 병렬 테스트 실행에 적합한지 확인해야합니다.
|
TestContext Framework Support Classes
이 section에서는 Spring TestContext Framework를 지원하는 다양한 class를 설명합니다.
Spring JUnit 4 Runner
Spring TestContext Framework는 custom runner를 통해 JUnit 4와의 완전한 통합을 제공합니다 (JUnit 4.12 이상에서 지원됨).
@RunWith(SpringJUnit4ClassRunner.class)
또는 더 짧은 @RunWith(SpringRunner.class)
변형으로 test class에 annotation을 달면 개발자는 표준 JUnit4 기반 단위 및 통합 테스트를 구현하는 동시에 application context 로드 지원, test instance의 dependency injection, transactional test method 실행 등 TestContext framework의 이점을 얻을 수 있습니다.
Spring TestContext Framework를 대체 runner(예: JUnit 4의 Parameterized
runner) 또는 third-party runners(예: MockitoJUnitRunner
)와 함께 사용하려는 경우 선택적으로 JUnit rule에 대한 Spring의 지원을 대신 사용할 수 있습니다
다음 코드 목록은 custom Spring Runner
로 실행할 test class를 구성하기위한 최소 요구 사항을 보여줍니다:
@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {
@Test
public void testMethod() {
// test logic...
}
}
@RunWith(SpringRunner::class)
@TestExecutionListeners
class SimpleTest {
@Test
fun testMethod() {
// test logic...
}
}
앞의 예제에서 @TestExecutionListeners
는 empty list로 구성되어 default listener를 비활성화합니다.
그렇지 않으면 @ContextConfiguration
을 통해 ApplicationContext
를 구성해야합니다.
Spring JUnit 4 Rules
org.springframework.test.context.junit4.rules
package는 다음 JUnit 4 rule을 제공합니다 (JUnit 4.12 이상에서 지원됨):
-
SpringClassRule
-
SpringMethodRule
SpringClassRule
은 Spring TestContext Framework의 class-level 기능을 지원하는 JUnit TestRule
이고 SpringMethodRule
은 Spring TestContext Framework의 instalce-level 및 method-level 기능을 지원하는 JUnit MethodRule
입니다.
SpringRunner
와 달리 Spring의 rule-based JUnit 지원은 org.junit.runner.Runner
구현과 독립적이라는 장점이 있으므로 기존 대체 runner(예: JUnit 4의 Parameterized
) 또는 third-party runner(예: MockitoJUnitRunner
)와 결합할 수 있습니다.
TestContext framework의 전체 기능을 지원하려면 SpringClassRule
을 SpringMethodRule
과 결합해야합니다.
다음 예는 통합 테스트에서 이러한 규칙을 선언하는 적절한 방법을 보여줍니다:
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
public class IntegrationTest {
@ClassRule
public static final SpringClassRule springClassRule = new SpringClassRule();
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
@Test
public void testMethod() {
// test logic...
}
}
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
class IntegrationTest {
@Rule
val springMethodRule = SpringMethodRule()
@Test
fun testMethod() {
// test logic...
}
companion object {
@ClassRule
val springClassRule = SpringClassRule()
}
}
JUnit 4 Support Classes
org.springframework.test.context.junit4
package는 JUnit 4 기반 테스트 케이스에 대해 다음 지원 클래스를 제공합니다 (JUnit 4.12 이상에서 지원됨).
-
AbstractJUnit4SpringContextTests
-
AbstractTransactionalJUnit4SpringContextTests
AbstractJUnit4SpringContextTests
는 JUnit 4 환경에서 명시적인 ApplicationContext
테스트 지원과 Spring TestContext Framework를 통합하는 추상 기본 테스트 클래스입니다. AbstractJUnit4SpringContextTests
를 확장할 때 명시적인 bean lookup을 수행하거나 전체 context의 상태를 테스트하는데 사용할 수 있는 protected
applicationContext
instance variable에 액세스 할 수 있습니다.
AbstractTransactionalJUnit4SpringContextTests
는 JDBC 액세스를 위한 몇 가지 편리한 기능을 추가하는 AbstractJUnit4SpringContextTests
의 abstract transactional extension입니다.
이 class는 javax.sql.DataSource
bean과 PlatformTransactionManager
bean이 ApplicationContext
에 정의될 것으로 예상합니다
AbstractTransactionalJUnit4SpringContextTests
를 확장하면 데이터베이스를 쿼리하기 위해 SQL 문을 실행하는 데 사용할 수 있는 protected
jdbcTemplate
instance variable에 액세스할 수 있습니다.
이러한 쿼리를 사용하여 데이터베이스 관련 애플리케이션 코드를 실행하기 전후에 데이터베이스 상태를 확인할 수 있으며 Spring은 이러한 쿼리가 애플리케이션 코드와 동일한 transaction 범위에서 실행되도록합니다.
ORM 도구와 함께 사용하는 경우 false positives를 피하십시오.
JDBC Testing Support에서 언급했듯이 AbstractTransactionalJUnit4SpringContextTests`는 앞서 언급한 `jdbcTemplate
을 사용하여 JdbcTestUtils
의 method에 위임하는 편리한 method도 제공합니다.
또한 AbstractTransactionalJUnit4SpringContextTests
는 구성된 DataSource
에 대해 SQL script를 실행하기위한 executeSqlScript(..)
method를 제공합니다.
이 class는 확장에 편리합니다.
test class가 Spring 특정 class hierarchy에 연결되는 것을 원하지 않는 경우 @RunWith(SpringRunner.class) 또는 Spring’s JUnit rules을 사용하여 custom test class를 구성 할 수 있습니다.
|
SpringExtension for JUnit Jupiter
Spring TestContext Framework는 JUnit 5에 도입된 JUnit Jupiter test framework와의 완전한 통합을 제공합니다.
@ExtendWith(SpringExtension.class)
로 test class에 annotation을 달면 표준 JUnit Jupiter 기반 단위 및 통합 테스트를 구현하고 동시에 application context 로드 지원, test inatance의 dependency injection, transactional test method 실행 등 TestContext framework의 이점을 얻을수 있습니다.
또한 JUnit Jupiter의 풍부한 확장 API 덕분에 Spring은 JUnit 4 및 TestNG에 대해 Spring이 지원하는 기능 set 이상의 다음 기능을 제공합니다:
-
test constructors, test methods 및 test lifecycle callback method에 대한 Dependency injection. 자세한 내용은 Dependency Injection with
SpringExtension
을 참조하십시오. -
SpEL expressions, environment variables, system properties 등을 기반으로 하는 conditional test execution을 강력하게 지원합니다. 자세한 내용과 예제는 Spring JUnit Jupiter Testing Annotations의
@EnabledIf
및@DisabledIf
에 대한 문서를 참조하십시오. -
Spring 및 JUnit Jupiter의 주석을 결합하는 custom composed annotations. 자세한 내용은 Meta-Annotation Support for Testing의
@TransactionalDevTestConfig
및@TransactionalIntegrationTest
예제를 참조하십시오.
다음 코드 목록은 @ContextConfiguration
과 함께 SpringExtension
을 사용하도록 test class를 구성하는 방법을 보여줍니다:
// Instructs JUnit Jupiter to extend the test with Spring support.
@ExtendWith(SpringExtension.class)
// Instructs Spring to load an ApplicationContext from TestConfig.class
@ContextConfiguration(classes = TestConfig.class)
class SimpleTests {
@Test
void testMethod() {
// test logic...
}
}
// Instructs JUnit Jupiter to extend the test with Spring support.
@ExtendWith(SpringExtension::class)
// Instructs Spring to load an ApplicationContext from TestConfig::class
@ContextConfiguration(classes = [TestConfig::class])
class SimpleTests {
@Test
fun testMethod() {
// test logic...
}
}
JUnit 5의 annotation도 meta-annotation으로 사용할 수 있으므로 Spring은 test ApplicationContext
및 JUnit Jupiter의 configuration을 단순화하기 위해 @SpringJUnitConfig
및 @SpringJUnitWebConfig
로 구성된 annotation을 제공합니다.
다음 예제는 @SpringJUnitConfig
를 사용하여 이전 예제에서 사용된 구성의 양을 줄입니다:
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load an ApplicationContext from TestConfig.class
@SpringJUnitConfig(TestConfig.class)
class SimpleTests {
@Test
void testMethod() {
// test logic...
}
}
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load an ApplicationContext from TestConfig.class
@SpringJUnitConfig(TestConfig::class)
class SimpleTests {
@Test
fun testMethod() {
// test logic...
}
}
마찬가지로 다음 예제에서는 @SpringJUnitWebConfig
를 사용하여 JUnit Jupiter와 함께 사용할 WebApplicationContext
를 만듭니다:
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load a WebApplicationContext from TestWebConfig.class
@SpringJUnitWebConfig(TestWebConfig.class)
class SimpleWebTests {
@Test
void testMethod() {
// test logic...
}
}
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load a WebApplicationContext from TestWebConfig::class
@SpringJUnitWebConfig(TestWebConfig::class)
class SimpleWebTests {
@Test
fun testMethod() {
// test logic...
}
}
자세한 내용은 Spring JUnit Jupiter Testing Annotations의 @SpringJUnitConfig
및 @SpringJUnitWebConfig
에 대한 문서를 참조하세요.
Dependency Injection with SpringExtension
SpringExtension
은 JUnit Jupiter의 ParameterResolver
extension API를 구현하여 Spring이 test constructors, test methods 및 test lifecycle callback method에 대한 dependency injection을 제공할 수 있도록 합니다.
특히 SpringExtension
은 테스트의 ApplicationContext
에서 @BeforeAll
, @AfterAll
, @BeforeEach
, @AfterEach
, @Test
, @RepeatedTest
, @ParameterizedTest
등으로 annotation이 달린 test constructor 및 method dependency를 inject 할 수 있습니다.
Constructor Injection
JUnit Jupiter test class에 대한 constructor의 특정 parameter가 ApplicationContext
(또는 그 sub-type) type이거나 @Autowired
, @Qualifier
또는 @Value`로 annotation 또는 meta-annotation이 추가 된 경우 Spring은 test의 `ApplicationContext
로부터 해당 bean 또는 value를 가진 특정 parameter를 value로 inject합니다.
constructor가 _autowired 가능_한 것으로 간주되는 경우 test class constructor에 대한 모든 argument를 autowire 하도록 Spring을 구성할 수도 있습니다. 다음 조건 중 하나가 충족되면 constructor는 _autowire 가능_한 것으로 간주됩니다 (우선 순위에 따라).
-
constructor가
@Autowired
로 annotation 처리 되었을 경우 -
@TestConstructor
가autowireMode
attribute가ALL
로 설정된 test class에 존재하거나 메타 존재할 경우 -
default _test constructor autowire mode_가
ALL
로 변경되었을 경우
@TestConstructor
사용 및 global _test constructor autowire mode_를 변경하는 방법에 대한 자세한 내용은 @TestConstructor
를 참조하십시오.
test class의 constructor가 _autowire 가능_한 것으로 간주되면 Spring은 constructor의 모든 parameter에 대한 argument를 해결하는 책임을 맡습니다.
따라서 JUnit Jupiter에 등록된 다른 ParameterResolver 는 이러한 constructor에 대한 parameter를 확인할 수 없습니다.
|
그 이유는
|
다음 예제에서 Spring은 TestConfig.class
에서 로드된 ApplicationContext
의 OrderService
bean을 OrderServiceIntegrationTests
constructor로 inject합니다.
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
private final OrderService orderService;
@Autowired
OrderServiceIntegrationTests(OrderService orderService) {
this.orderService = orderService;
}
// tests that use the injected OrderService
}
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests @Autowired constructor(private val orderService: OrderService){
// tests that use the injected OrderService
}
이 기능을 사용하면 test dependency는 final
이며 변경할 수 없습니다.
spring.test.constructor.autowire.mode
property가 all
(@TestConstructor
참조)이면 이전 예제의 constructor에서 @Autowired
선언을 생략하여 다음과 같은 결과를 얻을 수 있습니다.
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
private final OrderService orderService;
OrderServiceIntegrationTests(OrderService orderService) {
this.orderService = orderService;
}
// tests that use the injected OrderService
}
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests(val orderService:OrderService) {
// tests that use the injected OrderService
}
Method Injection
JUnit Jupiter test method 또는 test lifecycle callback method의 parameter가 ApplicationContext
(또는 그 sub-type) type이거나 @Autowired
, @Qualifier
또는 @Value
로 annotation 또는 meta-annotation이 달린 경우, Spring은 해당 특정 parameter의 값을 테스트의 ApplicationContext
에서 해당 bean으로 inject합니다.
다음 예제에서 Spring은 TestConfig.class
에서 로드된 ApplicationContext
의 OrderService
를 deleteOrder()
test method에 inject합니다:
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
@Test
void deleteOrder(@Autowired OrderService orderService) {
// use orderService from the test's ApplicationContext
}
}
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests {
@Test
fun deleteOrder(@Autowired orderService: OrderService) {
// use orderService from the test's ApplicationContext
}
}
JUnit Jupiter의 ParameterResolver
지원의 견고성으로 인해 Spring뿐만 아니라 JUnit Jupiter 자체 또는 third-party extension에서 단일 method에 여러 종속성을 주입 할 수도 있습니다.
다음 예제는 Spring과 JUnit Jupiter가 동시에 placeOrderRepeatedly()
test method에 종속성을 주입하는 방법을 보여줍니다.
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
@RepeatedTest(10)
void placeOrderRepeatedly(RepetitionInfo repetitionInfo,
@Autowired OrderService orderService) {
// use orderService from the test's ApplicationContext
// and repetitionInfo from JUnit Jupiter
}
}
@SpringJUnitConfig(TestConfig::class)
class OrderServiceIntegrationTests {
@RepeatedTest(10)
fun placeOrderRepeatedly(repetitionInfo:RepetitionInfo, @Autowired orderService:OrderService) {
// use orderService from the test's ApplicationContext
// and repetitionInfo from JUnit Jupiter
}
}
JUnit Jupiter에서 @RepeatedTest
를 사용하면 test method가 RepetitionInfo
에 액세스 할 수 있습니다.
@Nested
test class configuration
_Spring TestContext Framework_는 Spring Framework 5.0 이후 JUnit Jupiter의 @Nested
test class에서 test 관련 annotation 사용을 지원했습니다.
그러나 Spring Framework 5.3 class-level test configuration annotation이 superclass와 같이 enclosing class에서 _상속_되지 않았습니다.
Spring Framework 5.3은 enclosing class에서 test class configuration을 상속하는 first-class 지원을 도입했으며 이러한 구성은 기본적으로 상속됩니다.
기본 INHERIT
mode에서 OVERRIDE
mode로 변경하려면 @NestedTestConfiguration(EnclosingConfiguration.OVERRIDE)
을 사용하여 개별 @Nested
test class에 annotation을 달 수 있습니다.
명시적인 @NestedTestConfiguration
선언은 annotation이 달린 test class와 subclass 및 nested class에 적용됩니다.
따라서 @NestedTestConfiguration
으로 top-level test class에 annotation을 달 수 있으며 이는 모든 nested test class에 재귀적으로 적용됩니다.
개발팀이 기본값을 'OVERRIDE’로 변경하도록 허용하기 위해(예를 들어 Spring Framework 5.0 ~ 5.2와의 호환성을 위해) JVM system property 또는 classpath의 root에 있는 spring.properties
file을 통해 default mode를 전역적으로 변경할 수 있습니다.
자세한 내용은 "Changing
the default enclosing configuration inheritance mode" 참고를 참조하세요.
다음 "Hello World" 예제는 매우 단순하지만 @Nested
test class가 상속하는 top-level class에서 공통 구성을 선언하는 방법을 보여줍니다
이 특정 예에서는 TestConfig
configuration class만 상속됩니다.
각 중첩된 test class는 고유한 active profile set을 제공하므로 각 중첩된 test class에 대해 고유한 ApplicationContext
가 생성됩니다 (자세한 내용은 Context Caching 참조).
지원되는 annotation 목록을 참조하여 @Nested
test class에서 상속할 수 있는 annotation을 확인하십시오.
@SpringJUnitConfig(TestConfig.class)
class GreetingServiceTests {
@Nested
@ActiveProfiles("lang_en")
class EnglishGreetings {
@Test
void hello(@Autowired GreetingService service) {
assertThat(service.greetWorld()).isEqualTo("Hello World");
}
}
@Nested
@ActiveProfiles("lang_de")
class GermanGreetings {
@Test
void hello(@Autowired GreetingService service) {
assertThat(service.greetWorld()).isEqualTo("Hallo Welt");
}
}
}
@SpringJUnitConfig(TestConfig::class)
class GreetingServiceTests {
@Nested
@ActiveProfiles("lang_en")
inner class EnglishGreetings {
@Test
fun hello(@Autowired service:GreetingService) {
assertThat(service.greetWorld()).isEqualTo("Hello World")
}
}
@Nested
@ActiveProfiles("lang_de")
inner class GermanGreetings {
@Test
fun hello(@Autowired service:GreetingService) {
assertThat(service.greetWorld()).isEqualTo("Hallo Welt")
}
}
}
TestNG Support Classes
org.springframework.test.context.testng
package는 TestNG 기반 테스트 케이스에 대해 다음 지원 클래스를 제공합니다:
-
AbstractTestNGSpringContextTests
-
AbstractTransactionalTestNGSpringContextTests
AbstractTestNGSpringContextTests
는 Spring TestContext framework를 TestNG 환경에서 명시적인 ApplicationContext
테스트 지원과 통합하는 abstract base test class입니다.
AbstractTestNGSpringContextTests
를 확장할 때 명시적인 bean lookup을 수행하거나 전체 context의 상태를 테스트하는 데 사용할 수 있는 protected
applicationContext
instance variable에 액세스 할 수 있습니다.
AbstractTransactionalTestNGSpringContextTests
는 JDBC 액세스를 위한 몇 가지 편리한 기능을 추가하는 AbstractTestNGSpringContextTests
의 abstract transactional extension입니다.
이 class는 javax.sql.DataSource
bean과 PlatformTransactionManager
bean이 ApplicationContext
에 정의될 것으로 예상합니다.
AbstractTransactionalTestNGSpringContextTests
를 확장하면 데이터베이스를 쿼리하기 위해 SQL statement를 실행하는데 사용할 수 있는 protected
jdbcTemplate
instance variable에 액세스 할 수 있습니다.
이러한 쿼리를 사용하여 데이터베이스 관련 애플리케이션 코드를 실행하기 전후에 데이터베이스 상태를 확인할 수 있으며 Spring은 이러한 쿼리가 애플리케이션 코드와 동일한 transaction 범위에서 실행되도록 합니다.
ORM tool과 함께 사용하는 경우 false positives를 피하십시오.
JDBC Testing Support에서 언급했듯이 AbstractTransactionalTestNGSpringContextTests
는 앞서 언급한 jdbcTemplate
을 사용하여 JdbcTestUtils
의 method에 위임하는 편리한 method도 제공합니다.
또한 AbstractTransactionalTestNGSpringContextTests
는 구성된 DataSource
에 대해 SQL script를 실행하기 위한 executeSqlScript(..)
method를 제공합니다.
이 class는 확장에 편리합니다.
test class가 Spring 관련 class hierarchy에 연결되는 것을 원하지 않는 경우 @ContextConfiguration , @TestExecutionListeners 등을 사용하고 TestContextManager 로 test class를 수동으로 계측하여 custom test class를 구성 할 수 있습니다.
테스트 클래스를 계측하는 방법에 대한 예제는 AbstractTestNGSpringContextTests 의 소스 코드를 참조하십시오.
See the source code of AbstractTestNGSpringContextTests for an example of how to instrument your test class.
|
WebTestClient
WebTestClient
는 server application 테스트를 위해 설계된 HTTP client입니다.
Spring의 WebClient를 wrapping하고 이를 사용하여 요청을 수행하지만 응답 확인을 위한 testing facade를 노출합니다.
WebTestClient
를 사용하여 end-to-end HTTP 테스트를 수행 할 수 있습니다.
또한 mock server request 및 response object를 통해 running server없이 Spring MVC 및 Spring WebFlux application을 테스트 하는데 사용할 수도 있습니다.
Kotlin 사용자 : WebTestClient 사용과 관련된 이 section을 참조하세요.
|
Setup
WebTestClient
를 설정하려면 binding 할 서버 설정을 선택해야합니다.
이것은 여러 mock server 설정 중 하나이거나 live server에 대한 연결일 수 있습니다.
Bind to Controller
이 설정을 사용하면 running server없이 mock request 및 response object를 통해 특정 controller를 테스트 할 수 있습니다.
WebFlux application의 경우 WebFlux Java config에 해당하는 infrastructure를 로드하고 지정된 controller를 등록하고 요청을 처리하기위한 WebHandler chain을 만드는 다음을 사용합니다:
WebTestClient client =
WebTestClient.bindToController(new TestController()).build();
val client = WebTestClient.bindToController(TestController()).build()
Spring MVC의 경우 다음을 사용하여 StandaloneMockMvcBuilder에 위임하여 WebMvc Java config에 해당하는 infrastructure를 로드하고 주어진 controller를 등록하고 요청을 처리할 MockMvc instance를 만듭니다:
WebTestClient client =
MockMvcWebTestClient.bindToController(new TestController()).build();
val client = MockMvcWebTestClient.bindToController(TestController()).build()
Bind to ApplicationContext
이 설정을 통해 Spring MVC 또는 Spring WebFlux infrastructure 및 controller 선언으로 Spring 구성을 로드하고 이를 사용하여 running server없이 mock request 및 response object를 통해 요청을 처리할 수 있습니다.
WebFlux의 경우 다음을 사용하여 Spring ApplicationContext
가 WebHttpHandlerBuilder에 전달되어 요청을 처리할 WebHandler chain을 생성합니다:
@SpringJUnitConfig(WebConfig.class) (1)
class MyTests {
WebTestClient client;
@BeforeEach
void setUp(ApplicationContext context) { (2)
client = WebTestClient.bindToApplicationContext(context).build(); (3)
}
}
1 | Specify the configuration to load |
2 | Inject the configuration |
3 | Create the WebTestClient |
@SpringJUnitConfig(WebConfig::class) (1)
class MyTests {
lateinit var client: WebTestClient
@BeforeEach
fun setUp(context: ApplicationContext) { (2)
client = WebTestClient.bindToApplicationContext(context).build() (3)
}
}
1 | Specify the configuration to load |
2 | Inject the configuration |
3 | Create the WebTestClient |
Spring MVC의 경우 다음을 사용하여 Spring ApplicationContext
가 MockMvcBuilders.webAppContextSetup에 전달되어 요청을 처리할 MockMvc 인스턴스를 생성합니다:
@ExtendWith(SpringExtension.class)
@WebAppConfiguration("classpath:META-INF/web-resources") (1)
@ContextHierarchy({
@ContextConfiguration(classes = RootConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
class MyTests {
@Autowired
WebApplicationContext wac; (2)
WebTestClient client;
@BeforeEach
void setUp() {
client = MockMvcWebTestClient.bindToApplicationContext(this.wac).build(); (3)
}
}
1 | Specify the configuration to load |
2 | Inject the configuration |
3 | Create the WebTestClient |
@ExtendWith(SpringExtension.class)
@WebAppConfiguration("classpath:META-INF/web-resources") (1)
@ContextHierarchy({
@ContextConfiguration(classes = RootConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
class MyTests {
@Autowired
lateinit var wac: WebApplicationContext; (2)
lateinit var client: WebTestClient
@BeforeEach
fun setUp() { (2)
client = MockMvcWebTestClient.bindToApplicationContext(wac).build() (3)
}
}
1 | Specify the configuration to load |
2 | Inject the configuration |
3 | Create the WebTestClient |
Bind to Router Function
이 설정을 사용하면 running server없이 mock request 및 response object를 통해 functional endpoints를 테스트 할 수 있습니다.
WebFlux의 경우 RouterFunctions.toWebHandler
에 위임하는 다음을 사용하여 요청을 처리할 서버 설정을 만듭니다:
RouterFunction<?> route = ...
client = WebTestClient.bindToRouterFunction(route).build();
val route: RouterFunction<*> = ...
val client = WebTestClient.bindToRouterFunction(route).build()
Spring MVC의 경우 현재 WebMvc functional endpoints를 테스트하는 옵션이 없습니다.
Bind to Server
이 설정은 running server에 연결하여 전체 end-to-end HTTP 테스트를 수행합니다;
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build();
client = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build()
Client Config
앞에서 설명한 서버 설정 옵션 외에도 base URL, default headers, client filter 등을 포함한 client option을 구성 할 수 있습니다
이러한 옵션은 bindToServer()
다음에 쉽게 사용할 수 있습니다.
다른 모든 configuration option의 경우 다음과 같이 configureClient()
를 사용하여 server에서 client configuration으로 전환해야합니다:
client = WebTestClient.bindToController(new TestController())
.configureClient()
.baseUrl("/test")
.build();
client = WebTestClient.bindToController(TestController())
.configureClient()
.baseUrl("/test")
.build()
Writing Tests
WebTestClient
는 exchange()
를 사용하여 요청을 수행하는 시점까지 WebClient와 동일한 API를 제공합니다.
form data, multipart data 등을 포함한 모든 콘텐츠로 요청을 준비하는 방법에 대한 예제는 WebClient 설명서를 참조하십시오.
exchange()
호출 후 WebTestClient
는 WebClient
에서 분기되고 대신 응답을 확인하는 workflow를 계속합니다.
response status 및 header를 assert하려면 다음을 사용하십시오:
client.get().uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
client.get().uri("/persons/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
그런 다음 다음 중 하나를 통해 response body을 decoding 하도록 선택할 수 있습니다:
-
expectBody(Class<T>)
: Decode to single object. -
expectBodyList(Class<T>)
: Decode and collect objects toList<T>
. -
expectBody()
: Decode tobyte[]
for JSON Content or an empty body.
그리고 결과로 생성되는 higher level Object에 대해 assertion을 수행합니다:
client.get().uri("/persons")
.exchange()
.expectStatus().isOk()
.expectBodyList(Person.class).hasSize(3).contains(person);
client.get().uri("/persons")
.exchange()
.expectStatus().isOk()
.expectBodyList<Person>().hasSize(3).contains(person)
기본 제공 assertion이 충분하지 않으면 대신 object를 사용하고 다른 assertion을 수행할 수 있습니다:
client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody(Person.class)
.consumeWith(result -> {
// custom assertions (e.g. AssertJ)...
});
client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody<Person>()
.consumeWith {
// custom assertions (e.g. AssertJ)...
}
또는 workflow를 종료하고 EntityExchangeResult
를 얻을 수 있습니다:
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody(Person.class)
.returnResult();
val result = client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk
.expectBody<Person>()
.returnResult()
generic을 사용하여 대상 type으로 디코딩해야하는 경우 Class<T> 대신 ParameterizedTypeReference 를 허용하는 overload 된 method를 찾습니다.
|
No Content
응답에 내용이 없을 것으로 예상되는 경우 다음과 같이 assert 할 수 있습니다:
client.post().uri("/persons")
.body(personMono, Person.class)
.exchange()
.expectStatus().isCreated()
.expectBody().isEmpty();
client.post().uri("/persons")
.bodyValue(person)
.exchange()
.expectStatus().isCreated()
.expectBody().isEmpty()
응답 콘텐츠를 무시하려는 경우 다음은 assertion없이 콘텐츠를 해제합니다:
client.get().uri("/persons/123")
.exchange()
.expectStatus().isNotFound()
.expectBody(Void.class);
client.get().uri("/persons/123")
.exchange()
.expectStatus().isNotFound
.expectBody<Unit>()
JSON Content
대상 type없이 expectBody()
를 사용하여 higher level Object를 통하지 않고 raw content에 대한 assertion을 수행할 수 있습니다.
JSONAssert를 사용하여 전체 JSON content를 확인하려면:
client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody()
.json("{\"name\":\"Jane\"}")
client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody()
.json("{\"name\":\"Jane\"}")
JSONPath로 JSON content를 확인하려면:
client.get().uri("/persons")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$[0].name").isEqualTo("Jane")
.jsonPath("$[1].name").isEqualTo("Jason");
client.get().uri("/persons")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$[0].name").isEqualTo("Jane")
.jsonPath("$[1].name").isEqualTo("Jason")
Streaming Responses
"text/event-stream"
또는 "application/x-ndjson"
과 같이 잠재적으로 무한한 스트림을 테스트하려면 먼저 response status 및 header를 확인한 다음 FluxExchangeResult
를 가져옵니다:
FluxExchangeResult<MyEvent> result = client.get().uri("/events")
.accept(TEXT_EVENT_STREAM)
.exchange()
.expectStatus().isOk()
.returnResult(MyEvent.class);
val result = client.get().uri("/events")
.accept(TEXT_EVENT_STREAM)
.exchange()
.expectStatus().isOk()
.returnResult<MyEvent>()
이제 reactor-test
에서 StepVerifier
로 response stream을 사용할 준비가 되었습니다:
Flux<Event> eventFlux = result.getResponseBody();
StepVerifier.create(eventFlux)
.expectNext(person)
.expectNextCount(4)
.consumeNextWith(p -> ...)
.thenCancel()
.verify();
val eventFlux = result.getResponseBody()
StepVerifier.create(eventFlux)
.expectNext(person)
.expectNextCount(4)
.consumeNextWith { p -> ... }
.thenCancel()
.verify()
MockMvc Assertions
WebTestClient
는 HTTP client이므로 status, header 및 body를 포함하여 client response에 있는 내용만 확인할 수 있습니다.
MockMvc server 설정으로 Spring MVC application을 테스트 할 때 server response에 대한 extra choice를 수행할 수 있습니다.
body를 assert 한 후 ExchangeResult
를 가져오는 것으로 시작하려면 다음을 수행하십시오:
// For a response with a body
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody(Person.class)
.returnResult();
// For a response without a body
EntityExchangeResult<Void> result = client.get().uri("/path")
.exchange()
.expectBody().isEmpty();
// For a response with a body
val result = client.get().uri("/persons/1")
.exchange()
.expectStatus().isOk()
.expectBody(Person.class)
.returnResult();
// For a response without a body
val result = client.get().uri("/path")
.exchange()
.expectBody().isEmpty();
그런 다음 MockMvc server response assertion으로 전환합니다:
MockMvcWebTestClient.resultActionsFor(result)
.andExpect(model().attribute("integer", 3))
.andExpect(model().attribute("string", "a string value"));
MockMvcWebTestClient.resultActionsFor(result)
.andExpect(model().attribute("integer", 3))
.andExpect(model().attribute("string", "a string value"));
MockMvc
MockMvc라고도 하는 Spring MVC Test framework는 Spring MVC application 테스트를 지원합니다. 전체 Spring MVC 요청 처리를 수행하지만 실행 중인 서버 대신 mock request 및 response object를 통해 수행합니다.
MockMvc는 자체적으로 요청을 수행하고 응답을 확인하는데 사용할 수 있습니다.
MockMvc가 요청을 처리할 서버로 연결되는 WebTestClient 를 통해 사용할수도 있습니다
WebTestClient
의 장점은 raw data 대신 higher level objects로 작업할 수 있는 옵션과 live server에 대해 완전한 end-to-end HTTP 테스트로 전환하고 동일한 테스트 API를 사용하는 기능입니다.
Overview
controller를 instance화하고 dependency를 inject하고 method를 호출하여 Spring MVC에 대한 일반 단위 테스트를 작성할 수 있습니다.
그러나 이러한 테스트는 request mapping, data binding, message conversion, type conversion, validation을 확인하지 않으며 지원하는 @InitBinder
, @ModelAttribute
또는 @ExceptionHandler
method를 포함하지도 않습니다.
MockMvc
라고도 하는 Spring MVC Test framework는 running server없이 Spring MVC controller에 대한 보다 완전한 테스트를 제공하는 것을 목표로합니다.
DispacherServlet
을 호출하고 running server 없이 전체 Spring MVC request 처리를 복제하는 spring-test
module에서 the Servlet API의 “mock” 구현을 전달하여 이를 수행합니다.
MockMvc
는 lightweight 및 targeted test를 사용하여 Spring MVC application의 기능 대부분을 검증할 수 있는 서버 측 test framework입니다.
요청을 수행하고 응답을 확인하기위해 자체적으로 사용하거나 요청을 처리할 서버로 연결된 MockMvc와 함께 WebTestClient API를 통해 사용할 수도 있습니다.
Static Imports
MockMvc를 직접 사용하여 요청을 수행하는 경우 다음에 대한 static import가 필요합니다:
-
MockMvcBuilders.*
-
MockMvcRequestBuilders.*
-
MockMvcResultMatchers.*
-
MockMvcResultHandlers.*
이를 기억하는 쉬운 방법은 MockMvc*
를 검색하는 것입니다.
Eclipse를 사용하는 경우 Eclipse 환경 설정에서 위의 “favorite static members” 도 추가해야합니다.
WebTestClient 를 통해 MockMvc를 사용할 때 static import가 필요하지 않습니다.
WebTestClient
는 static import없이 유창한 API를 제공합니다.
Setup Choices
MockMvc는 2가지 방법 중 하나로 설정할 수 있습니다. 하나는 테스트하려는 컨트롤러를 직접 가리키고 Spring MVC infrastructure를 프로그래밍 방식으로 구성하는 것입니다. 두 번째는 Spring MVC 및 controller infrastructure가 포함된 Spring configuration을 가리키는 것입니다.
특정 controller를 테스트하기 위해 MockMvc를 설정하려면 다음을 사용하십시오:
class MyWebTests {
MockMvc mockMvc;
@BeforeEach
void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
}
// ...
}
class MyWebTests {
lateinit var mockMvc : MockMvc
@BeforeEach
fun setup() {
mockMvc = MockMvcBuilders.standaloneSetup(AccountController()).build()
}
// ...
}
또는 위에 표시된 것과 동일한 build에 위임하는 WebTestClient를 통해 테스트 할때도 이 설정을 사용할 수 있습니다.
Spring configuration을 통해 MockMvc를 설정하려면 다음을 사용하십시오:
@SpringJUnitWebConfig(locations = "my-servlet-context.xml")
class MyWebTests {
MockMvc mockMvc;
@BeforeEach
void setup(WebApplicationContext wac) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
// ...
}
@SpringJUnitWebConfig(locations = ["my-servlet-context.xml"])
class MyWebTests {
lateinit var mockMvc: MockMvc
@BeforeEach
fun setup(wac: WebApplicationContext) {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
}
// ...
}
또는 위에 표시된 것과 동일한 builder에 위임하는 WebTestClient를 통해 테스트 할때도 이 설정을 사용할 수 있습니다.
어떤 setup option을 사용해야합니까?
webAppContextSetup
은 실제 Spring MVC configuration을 로드하여 보다 완전한 통합 테스트를 수행합니다.
TestContext framework는 로드된 Spring configuration을 cache하기 때문에 test suite에 더 많은 테스트를 도입하더라도 테스트를 빠르게 실행하는데 도움이됩니다.
또한 Spring 구성을 통해 controller에 mock service를 inject하여 web layer test에 계속 집중할 수 있습니다.
다음 예제는 Mockito로 mock service를 선언합니다:
<bean id="accountService" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.example.AccountService"/>
</bean>
그런 다음 mock service를 테스트에 inject하여 다음 예제와 같이 기대치를 설정하고 확인할 수 있습니다:
@SpringJUnitWebConfig(locations = "test-servlet-context.xml")
class AccountTests {
@Autowired
AccountService accountService;
MockMvc mockMvc;
@BeforeEach
void setup(WebApplicationContext wac) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
// ...
}
@SpringJUnitWebConfig(locations = ["test-servlet-context.xml"])
class AccountTests {
@Autowired
lateinit var accountService: AccountService
lateinit mockMvc: MockMvc
@BeforeEach
fun setup(wac: WebApplicationContext) {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
}
// ...
}
반면에 standaloneSetup
은 단위 테스트에 조금 더 가깝습니다.
한 번에 하나의 컨트롤러를 테스트합니다.
수동으로 controller에 mock dependency을 inject 할 수 있으며 Spring configuration 로드를 포함하지 않습니다.
이러한 테스트는 스타일에 더 초점을 맞추고 어떤 controller가 테스트 되고 있는지, 특정 Spring MVC configuration이 작동해야 하는지 여부 등을 쉽게 확인할 수 있습니다.
standaloneSetup
은 또한 특정 동작을 확인하거나 문제를 디버깅하기 위해 임시 테스트를 작성하는 매우 편리한 방법입니다.
대부분의 “integration versus unit testing” 논쟁과 마찬가지로 정답이나 오답은 없습니다.
그러나 standaloneSetup
을 사용하면 Spring MVC configuration을 확인하기 위해 추가 webAppContextSetup
테스트가 필요합니다.
또는 실제 Spring MVC configuration에 대해 항상 테스트하기 위해 webAppContextSetup
으로 모든 테스트를 작성할 수 있습니다.
Setup Features
어떤 MockMvc builder를 사용하든 모든 MockMvcBuilder
구현은 공통적이고 매우 유용한 기능을 제공합니다.
예를 들어 모든 요청에 대해 Accept header를 선언하고 다음과 같이 모든 응답에서 상태 200과 Content-Type header를 기대할 수 있습니다:
// static import of MockMvcBuilders.standaloneSetup
MockMvc mockMvc = standaloneSetup(new MusicController())
.defaultRequest(get("/").accept(MediaType.APPLICATION_JSON))
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType("application/json;charset=UTF-8"))
.build();
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
또한 third-party framework(및 application)는 MockMvcConfigurer
에 있는 것과 같은 설정 지침을 미리 package화 할 수 있습니다.
Spring Framework에는 요청 간에 HTTP session을 저장하고 재사용하는데 도움이되는 이러한 내장 구현이 하나 있습니다.
다음과 같이 사용할 수 있습니다:
// static import of SharedHttpSessionConfigurer.sharedHttpSession
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TestController())
.apply(sharedHttpSession())
.build();
// Use mockMvc to perform requests...
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
모든 MockMvc builder 기능 목록은 ConfigurableMockMvcBuilder
의 javadoc을 참조하거나 IDE를 사용하여 사용가능한 옵션을 탐색하십시오.
Performing Requests
이 섹션에서는 MockMvc 자체를 사용하여 요청을 수행하고 응답을 확인하는 방법을 보여줍니다.
WebTestClient
를 통해 MockMvc를 사용하는 경우에는 Writing Tests의 해당 section을 참조하십시오.
다음 예제와 같이 HTTP method를 사용하는 요청을 수행하려면:
// static import of MockMvcRequestBuilders.*
mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON));
mockMvc.post("/hotels/{id}", 42) {
accept = MediaType.APPLICATION_JSON
}
multipart request의 실제 구문 분석이 없도록 내부적으로 MockMultipartHttpServletRequest
를 사용하는 file upload request을 수행할 수도 있습니다.
대신 다음 예제와 유사하게 설정해야합니다:
mockMvc.perform(multipart("/doc").file("a1", "ABC".getBytes("UTF-8")));
mockMvc.multipart("/doc") {
file("a1", "ABC".toByteArray(charset("UTF8")))
}
다음 예와 같이 URI template style로 query parameter를 지정할 수 있습니다:
mockMvc.perform(get("/hotels?thing={thing}", "somewhere"));
mockMvc.get("/hotels?thing={thing}", "somewhere")
다음 예와 같이 query 또는 form parameter를 나타내는 Servlet request parameter를 추가 할 수도 있습니다:
mockMvc.perform(get("/hotels").param("thing", "somewhere"));
mockMvc.get("/hotels") {
param("thing", "somewhere")
}
application code가 Servlet request parameter에 의존하고 query string을 명시적으로 확인하지 않는 경우(대부분의 경우) 사용하는 option은 중요하지 않습니다.
단, URI template과 함께 제공되는 query parameter는 decoding 되는 반면 param(…)
method를 통해 제공되는 request parameter는 이미 decoding 될 것으로 예상됩니다.
대부분의 경우 context path와 servlet path는 request URI에서 제외하는 것이 좋습니다.
full request URI로 테스트 해야하는 경우 다음 예제와 같이 request mapping이 작동하도록 contextPath
및 servletPath
를 적절하게 설정해야합니다:
mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main"))
mockMvc.get("/app/main/hotels/{id}") {
contextPath = "/app"
servletPath = "/main"
}
앞의 예에서는 수행된 모든 request에 대해 contextPath
및 servletPath
를 설정하는 것이 번거롭습니다.
대신 다음 예제와 같이 default request properties를 설정할 수 있습니다:
class MyWebTests {
MockMvc mockMvc;
@BeforeEach
void setup() {
mockMvc = standaloneSetup(new AccountController())
.defaultRequest(get("/")
.contextPath("/app").servletPath("/main")
.accept(MediaType.APPLICATION_JSON)).build();
}
}
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
앞의 properties는 MockMvc
instance를 통해 수행되는 모든 request에 영향을 줍니다.
지정된 request에도 동일한 property가 지정되면 기본값이 override 됩니다.
이것이 기본 요청의 HTTP method와 URI가 모든 request에 지정되어야하기 때문에 중요하지 않은 이유입니다.
따라서 모든 request에 대해 HTTP method와 URI를 지정해야 하므로 default request의 URI는 중요하지 않습니다.
Defining Expectations
다음 예제와 같이 요청을 수행한 후 하나 이상의 .andExpect(..)
호출을 추가하여 기대치를 정의할 수 있습니다:
// static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.*
mockMvc.perform(get("/accounts/1")).andExpect(status().isOk());
mockMvc.get("/accounts/1").andExpect {
status().isOk()
}
MockMvcResultMatchers.*
는 많은 기대치를 제공하며, 그 중 일부는 더 자세한 기대치로 더 중첩됩니다.
기대치는 두 가지 일반적인 범주로 나뉩니다. 첫 번째 assertion 범주는 response의 properties (예: response status, header 및 content)을 확인합니다 이것은 assert해야 할 가장 중요한 결과입니다.
두 번째 assertion 범주는 response를 벗어납니다. 이러한 assertion을 통해 request를 처리한 controller method, exception 발생 및 처리 여부, model의 content, 선택된 view, 추가된 flash attribute 등과 같은 Spring MVC 특정 측면을 검사할 수 있습니다. 또한 request 및 session attribute과 같은 servlet 특정 측면을 검사할 수 있습니다.
다음 테스트는 binding 또는 validation 검사가 실패했다고 assert 합니다:
mockMvc.perform(post("/persons"))
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("person"));
mockMvc.post("/persons").andExpect {
status().isOk()
model {
attributeHasErrors("person")
}
}
여러번 테스트를 작성할 때 수행된 요청의 결과를 domp하는 것이 유용합니다.
다음과 같이 할 수 있습니다.
여기서 print()
는 MockMvcResultHandlers
에서 static import입니다:
mockMvc.perform(post("/persons"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("person"));
mockMvc.post("/persons").andDo {
print()
}.andExpect {
status().isOk()
model {
attributeHasErrors("person")
}
}
요청 처리로 인해 처리되지 않은 exception이 발생하지 않는 한 print()
method는 사용 가능한 모든 결과 데이터를 System.out
에 print합니다
또한 log()
method와 print()
method의 두 가지 추가 변형이 있습니다.
하나는 OutputStream
을 받아들이고 다른 하나는 Writer
를 받아들이는 것입니다.
예를 들어, print(System.err)
를 호출하면 결과 데이터가 System.err
에 print 되고 print(myWriter)
를 호출하면 결과 데이터가 custom writer에 print 됩니다.
결과 데이터를 print 하는 대신 log 하려면 log()
method를 호출하면 결과 데이터가 org.springframework.test.web.servlet.result
logging category 아래에 단일 DEBUG
메시지로 기록됩니다.
어떤 경우에는 결과에 직접 액세스하고 다른 방법으로는 확인할 수 없는 것을 확인해야 할 수 있습니다.
다음 예제와 같이 다른 모든 expectation 뒤에 .andReturn()
을 추가하면됩니다:
MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn();
// ...
var mvcResult = mockMvc.post("/persons").andExpect { status().isOk() }.andReturn()
// ...
모든 테스트가 동일한 expectation를 반복하는 경우 다음 예제와 같이 MockMvc
instance를 빌드할 때 common expectation을 한 번 설정할 수 있습니다:
standaloneSetup(new SimpleController())
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType("application/json;charset=UTF-8"))
.build()
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
common expectation은 항상 적용되며 별도의 MockMvc
instance를 생성하지 않고는 override 할 수 없습니다.
JSON response content에 Spring HATEOAS로 생성된 hypermedia link가 포함된 경우 다음 예제와 같이 JsonPath expression을 사용하여 결과 링크를 확인할 수 있습니다:
mockMvc.perform(get("/people").accept(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.links[?(@.rel == 'self')].href").value("http://localhost:8080/people"));
mockMvc.get("/people") {
accept(MediaType.APPLICATION_JSON)
}.andExpect {
jsonPath("$.links[?(@.rel == 'self')].href") {
value("http://localhost:8080/people")
}
}
XML response content에 Spring HATEOAS로 생성된 hypermedia link가 포함된 경우 XPath expression을 사용하여 결과 링크를 확인할 수 있습니다:
Map<String, String> ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom");
mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML))
.andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string("http://localhost:8080/people"));
val ns = mapOf("ns" to "http://www.w3.org/2005/Atom")
mockMvc.get("/handle") {
accept(MediaType.APPLICATION_XML)
}.andExpect {
xpath("/person/ns:link[@rel='self']/@href", ns) {
string("http://localhost:8080/people")
}
}
Async Requests
이 section에서는 asynchronous request handling을 테스트하기 위해 MockMvc 자체를 사용하는 방법을 보여줍니다.
WebTestClient를 통해 MockMvc를 사용하는 경우 WebTestClient
가 이 section에서 설명하는 작업을 자동으로 수행하므로 asynchronous request가 작동하도록 하기위해 특별히 수행할 작업이 없습니다.
Spring MVC 에서 지원되는 Servlet 3.0 asynchronous request는 Servlet container thread를 종료하고 application이 response를 비동기적으로 계산할 수 있도록 허용한 후 async dispatch가 Servlet container thread에서 처리를 완료하도록 만들어집니다.
Spring MVC test에서는 생성된 async value를 먼저 assert한 다음 수동으로 async dispatch를 수행하고 마지막으로 response를 확인하여 async request를 테스트할 수 있습니다.
다음은 DeferredResult
, Callable
또는 Reactor Mono
와 같은 reactive type을 반환하는 controller method에 대한 예제 테스트입니다:
// static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.*
@Test
void test() throws Exception {
MvcResult mvcResult = this.mockMvc.perform(get("/path"))
.andExpect(status().isOk()) (1)
.andExpect(request().asyncStarted()) (2)
.andExpect(request().asyncResult("body")) (3)
.andReturn();
this.mockMvc.perform(asyncDispatch(mvcResult)) (4)
.andExpect(status().isOk()) (5)
.andExpect(content().string("body"));
}
1 | response status가 여전히 변경되지 않았는지 확인 |
2 | async processing가 시작되어야합니다 |
3 | async result를 기다렸다가 assert |
4 | ASYNC dispatch를 수동으로 수행합니다. (running container가 없기 때문에) |
5 | final response 확인 |
@Test
fun test() {
var mvcResult = mockMvc.get("/path").andExpect {
status().isOk() (1)
request { asyncStarted() } (2)
// TODO Remove unused generic parameter
request { asyncResult<Nothing>("body") } (3)
}.andReturn()
mockMvc.perform(asyncDispatch(mvcResult)) (4)
.andExpect {
status().isOk() (5)
content().string("body")
}
}
1 | response status가 여전히 변경되지 않았는지 확인 |
2 | async processing가 시작되어야합니다 |
3 | async result를 기다렸다가 assert |
4 | ASYNC dispatch를 수동으로 수행합니다. (running container가 없기 때문에) |
5 | final response 확인 |
Streaming Responses
Server-Sent Events와 같은 streaming response를 테스트하는 가장 좋은 방법은 running server없이 Spring MVC controller에서 테스트를 수행하기 위해 MockMvc
instance에 연결하는 test client로 사용할 수 있는 WebTestClient를 사용하는 것입니다.
예를 들면:
WebTestClient client = MockMvcWebTestClient.bindToController(new SseController()).build();
FluxExchangeResult<Person> exchangeResult = client.get()
.uri("/persons")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType("text/event-stream")
.returnResult(Person.class);
// Project Reactor의 StepVerifier를 사용하여 streaming response 테스트
StepVerifier.create(exchangeResult.getResponseBody())
.expectNext(new Person("N0"), new Person("N1"), new Person("N2"))
.expectNextCount(4)
.consumeNextWith(person -> assertThat(person.getName()).endsWith("7"))
.thenCancel()
.verify();
WebTestClient
는 live server에 연결하여 완전한 end-to-end 통합 테스트를 수행할 수도 있습니다.
running server를 테스트 할 수 있는 Spring Boot에서도 지원됩니다.
Filter Registrations
MockMvc
instance를 설정할 때 다음 예제와 같이 하나 이상의 Servlet Filter
instance를 등록 할 수 있습니다:
mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build();
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
등록된 필터는 spring-test
에서 MockFilterChain
을 통해 호출되고 마지막 filter는 DispatcherServlet
에 위임됩니다.
MockMvc vs End-to-End Tests
MockMVc는 spring-test
module의 Servlet API mock 구현을 기반으로 하며 running container에 의존하지 않습니다.
따라서 실제 client와 실행 중인 live server를 사용한 전체 end-to-end 통합 테스트와 비교할 때 약간의 차이가 있습니다.
이 문제에 대해 가장 쉽게 생각할 수 있는 방법은 blank MockHttpServletRequest
로 시작하는 것입니다.
무엇을 추가하든 request가 됩니다.
놀라운 점은 기본적으로 context path가 없다는 것입니다.
no jsessionid
cookie; no forwarding, error, 또는 async dispatches;
따라서 actual JSP rendering이 없습니다.
대신 “forwarded” 및 “redirected” URL은 MockHttpServletResponse
에 저장되며 expectation과 함께 assert 할 수 있습니다.
즉, JSP를 사용하는 경우 request가 전달된 JSP page를 확인할 수 있지만 HTML은 rendering 되지 않습니다.
즉, JSP가 호출되지 않습니다
그러나 Thymeleaf 및 Freemarker와 같이 전달에 의존하지 않는 다른 모든 rendering 기술은 예상대로 HTML을 response body에 rendering 합니다.
@ResponseBody
method를 통해 JSON, XML 및 기타 형식을 rendering 하는 경우에도 마찬가지입니다.
또는 @SpringBootTest
를 사용하는 Spring Boot의 full end-to-end 통합 테스트 지원을 고려할 수 있습니다.
Spring Boot Reference Guide를 참조하세요.
각 접근 방식에는 장단점이 있습니다.
Spring MVC Test에서 제공하는 옵션은 클래식 단위 테스트에서 전체 통합 테스트까지 규모가 다릅니다.
확실히 Spring MVC Test의 어떤 옵션도 고전적인 단위 테스트의 범주에 속하지 않지만 조금 더 가깝습니다.
예를 들어, controller에 mock service를 inject하여 web layer를 격리 할 수 있습니다.
이 경우 DispatcherServlet
을 통해서만 web layer를 테스트하지만 실제 Spring configuration을 사용하여 그 위에 있는 계층과 격리 된 데이터 액세스 계층을 테스트 할 수 있습니다.
또한 한 번에 하나의 controller에 초점을 맞추고 작동하는 데 필요한 configuration을 수동으로 제공하는 stand-alone 설정을 사용할 수 있습니다.
Spring MVC Test를 사용할 때 또 다른 중요한 차이점은 개념적으로 이러한 테스트는 server-side이므로 어떤 handler가 사용되었는지, HandlerExceptionResolver로 exception이 처리되었는지, model의 내용이 무엇인지, binding error가 무엇인지 및 기타 세부 사항을 check 할 수 있다는 것입니다. 이는 실제 HTTP client를 통해 테스트 할 때처럼 서버가 불투명 한 box가 아니기 때문에 expectation을 작성하는 것이 더 쉽다는 것을 의미합니다. 이것은 일반적으로 고전적인 단위 테스트의 장점입니다. 작성, 추론 및 디버그가 더 쉽지만 완전한 통합 테스트의 필요성을 대체하지는 않습니다. 동시에, 응답이 가장 중요한 확인 사항이라는 사실을 놓치지 않는 것이 중요합니다. 요컨대, 동일한 프로젝트 내에서도 여러 스타일과 테스트 전략을 위한 여지가 있습니다.
Further Examples
프레임 워크의 자체 테스트에는 자체적으로 또는 WebTestClient를 통해 MockMvc를 사용하는 방법을 보여주기위한 many sample tests가 포함되어 있습니다. 추가 아이디어를 보려면 이 예제를 찾아보십시오.
HtmlUnit Integration
Spring은 MockMvc와 HtmlUnit 간의 통합을 제공합니다. 이는 HTML 기반 view를 사용할 때 end-to-end 테스트 수행을 단순화합니다. 이 통합을 통해 다음을 수행 할 수 있습니다:
MockMvc는 Servlet container(예: Thymeleaf, FreeMarker 등)에 의존하지 않는 templating 기술과 함께 작동하지만 JSP는 Servlet container에 의존하기 때문에 작동하지 않습니다. |
Why HtmlUnit Integration?
떠오르는 가장 분명한 질문은 "왜 이것이 필요한가요?"입니다.
매우 기본적인 sample application을 살펴보면 답을 찾을 수 있습니다.
Message
object에서 CRUD 작업을 지원하는 Spring MVC web application이 있다고 가정합니다.
application은 또한 모든 message를 통한 페이징을 지원합니다.
테스트는 어떻게 하시겠습니까?
Spring MVC 테스트를 사용하면 다음과 같이 message를 생성할 수 있는지 쉽게 테스트 할 수 있습니다:
MockHttpServletRequestBuilder createMessage = post("/messages/")
.param("summary", "Spring Rocks")
.param("text", "In case you didn't know, Spring Rocks!");
mockMvc.perform(createMessage)
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/messages/123"));
@Test
fun test() {
mockMvc.post("/messages/") {
param("summary", "Spring Rocks")
param("text", "In case you didn't know, Spring Rocks!")
}.andExpect {
status().is3xxRedirection()
redirectedUrl("/messages/123")
}
}
message를 생성할 수있는 form view를 테스트하려면 어떻게해야합니까? 예를 들어, form이 다음 snippet과 같다고 가정합니다:
<form id="messageForm" action="/messages/" method="post">
<div class="pull-right"><a href="/messages/">Messages</a></div>
<label for="summary">Summary</label>
<input type="text" class="required" id="summary" name="summary" value="" />
<label for="text">Message</label>
<textarea id="text" name="text"></textarea>
<div class="form-actions">
<input type="submit" value="Create" />
</div>
</form>
form이 새 message를 작성하기위한 올바른 request을 생성하는지 어떻게 확인합니까? 순진한 시도는 다음과 유사 할 수 있습니다:
mockMvc.perform(get("/messages/form"))
.andExpect(xpath("//input[@name='summary']").exists())
.andExpect(xpath("//textarea[@name='text']").exists());
mockMvc.get("/messages/form").andExpect {
xpath("//input[@name='summary']") { exists() }
xpath("//textarea[@name='text']") { exists() }
}
이 테스트에는 몇 가지 명백한 단점이 있습니다.
text
대신 parameter message
를 사용하도록 controller를 업데이트하면 HTML form이 controller와 동기화되지 않더라도 form 테스트가 계속 통과됩니다.
이를 해결하기 위해 다음과 같이 두 가지 테스트를 결합 할 수 있습니다:
String summaryParamName = "summary";
String textParamName = "text";
mockMvc.perform(get("/messages/form"))
.andExpect(xpath("//input[@name='" + summaryParamName + "']").exists())
.andExpect(xpath("//textarea[@name='" + textParamName + "']").exists());
MockHttpServletRequestBuilder createMessage = post("/messages/")
.param(summaryParamName, "Spring Rocks")
.param(textParamName, "In case you didn't know, Spring Rocks!");
mockMvc.perform(createMessage)
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/messages/123"));
val summaryParamName = "summary";
val textParamName = "text";
mockMvc.get("/messages/form").andExpect {
xpath("//input[@name='$summaryParamName']") { exists() }
xpath("//textarea[@name='$textParamName']") { exists() }
}
mockMvc.post("/messages/") {
param(summaryParamName, "Spring Rocks")
param(textParamName, "In case you didn't know, Spring Rocks!")
}.andExpect {
status().is3xxRedirection()
redirectedUrl("/messages/123")
}
이렇게 하면 테스트가 잘못 통과될 위험이 줄어들지만 여전히 몇 가지 문제가 있습니다:
-
페이지에 여러 form이 있으면 어떻게됩니까? 물론 XPath expression을 업데이트 할 수 있지만 더 많은 요소를 고려할수록 더 복잡해집니다. field가 활성화되어 있습니까? 등등.
-
또 다른 문제는 우리가 예상했던 작업을 두 배로 늘리고 있다는 것입니다. 먼저 view를 확인한 다음 방금 확인한 것과 동일한 parameters를 사용하여 view를 제출합니다. 이상적으로는 이 모든 작업을 한 번에 수행 할 수 있습니다.
-
마지막으로 우리는 여전히 몇 가지를 설명할 수 없습니다. 예를 들어 form에 테스트하려는 JavaScript 유효성 검사가있는 경우 어떻게 해야 합니까?
전체적인 문제는 웹 페이지 테스트가 단일 상호 작용을 포함하지 않는다는 것입니다. 대신 사용자가 웹 페이지와 상호 작용하는 방식과 해당 웹 페이지가 다른 리소스와 상호 작용하는 방식의 조합입니다. 예를 들어, form view의 결과는 message 작성을 위해 사용자에 대한 입력으로 사용됩니다. 또한 form view는 JavaScript 유효성 검사와 같이 페이지 동작에 영향을 주는 추가 리소스를 잠재적으로 사용할 수 있습니다.
Integration Testing to the Rescue?
앞서 언급 한 문제를 해결하기 위해 end-to-end 통합 테스트를 수행 할 수 있지만 여기에는 몇 가지 단점이 있습니다. message를 통해 페이지를 볼 수 있는 view를 테스트 해보십시오. 다음 테스트가 필요할 수 있습니다:
-
message가 비어있을 때 사용할 수 있는 결과가 없음을 나타내는 알림이 페이지에 사용자에게 표시됩니까?
-
페이지에 단일 message가 제대로 표시됩니까?
-
페이지가 페이징을 제대로 지원합니까?
이러한 테스트를 설정하려면 데이터베이스에 적절한 message가 포함되어 있는지 확인해야합니다. 이로 인해 여러 가지 추가 과제가 발생합니다:
-
데이터베이스에 적절한 message가 있는지 확인하는 것은 지루할 수 있습니다. (foreign key 제약을 고려하십시오.)
-
각 테스트는 데이터베이스가 올바른 상태인지 확인해야하기 때문에 테스트 속도가 느려질 수 있습니다.
-
데이터베이스가 특정 상태에 있어야하므로 테스트를 병렬로 실행할 수 없습니다.
-
auto-generated id, timestamp 및 기타 항목에 대해 assertion을 수행하는 것은 어려울 수 있습니다.
이러한 문제가 end-to-end 통합 테스트를 완전히 포기해야한다는 의미는 아닙니다. 대신, 훨씬 더 빠르고 안정적이며 부작용없이 실행되는 mock service를 사용하도록 세부 테스트를 리팩토링하여 end-to-end 통합 테스트의 수를 줄일 수 있습니다. 그런 다음 모든 것이 제대로 작동하는지 확인하기 위해 간단한 workflow를 검증하는 소수의 진정한 end-to-end 통합 테스트를 구현할 수 있습니다.
Enter HtmlUnit Integration
그렇다면 페이지의 상호 작용을 테스트하고 test suite 내에서 좋은 성능을 유지하는 것 사이의 균형을 어떻게 유지할 수 있습니까? 대답은 “MockMvc를 HtmlUnit과 통합함으로써” 입니다.
HtmlUnit Integration Options
MockMvc를 HtmlUnit과 통합하려는 경우 여러 옵션이 있습니다:
-
MockMvc and HtmlUnit: raw HtmlUnit library를 사용하려면이 옵션을 사용하십시오.
-
MockMvc and WebDriver: 이 옵션을 사용하여 통합과 end-to-end 테스트간에 개발을 쉽게하고 코드를 재사용할 수 있습니다.
-
MockMvc and Geb: 테스트를 위해 Groovy를 사용하고, 개발을 용이하게하고, 통합과 end-to-end 테스트간에 코드를 재사용하려면이 옵션을 사용하십시오.
MockMvc and HtmlUnit
이 section에서는 MockMvc와 HtmlUnit을 통합하는 방법을 설명합니다. raw HtmlUnit 라이브러리를 사용하려면 이 옵션을 사용하십시오.
MockMvc and HtmlUnit Setup
먼저 net.sourceforge.htmlunit:htmlunit
에 대한 test dependency를 포함했는지 확인하십시오.
Apache HttpComponents 4.5 이상에서 HtmlUnit을 사용하려면 HtmlUnit 2.18 이상을 사용해야합니다.
다음과 같이 MockMvcWebClientBuilder
를 사용하여 MockMvc와 통합되는 HtmlUnit WebClient
를 쉽게 만들 수 있습니다:
WebClient webClient;
@BeforeEach
void setup(WebApplicationContext context) {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.build();
}
lateinit var webClient: WebClient
@BeforeEach
fun setup(context: WebApplicationContext) {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.build()
}
이것은 MockMvcWebClientBuilder 를 사용하는 간단한 예입니다.
고급 사용에 대해서는 Advanced MockMvcWebClientBuilder 를 참조하세요.
|
이렇게 하면 실제 HTTP connection없이 localhost
를 server로 참조하는 모든 URL이 MockMvc
instance로 전달됩니다.
다른 모든 URL은 정상적으로 network connection을 사용하여 요청됩니다.
이를 통해 CDN 사용을 쉽게 테스트 할 수 있습니다.
MockMvc and HtmlUnit Usage
이제 우리는 평소처럼 HtmlUnit을 사용할 수 있지만 application을 Servlet container에 배포 할 필요가 없습니다. 예를 들어, 다음과 같은 message를 생성하도록 view를 요청할 수 있습니다:
HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form");
val createMsgFormPage = webClient.getPage("http://localhost/messages/form")
default context path는 "" 입니다.
또는 Advanced MockMvcWebClientBuilder 에 설명 된대로 context path를 지정할 수 있습니다.
|
HtmlPage
에 대한 참조가 있으면 다음 예제와 같이 form을 작성하고 제출하여 message를 만들 수 있습니다:
HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm");
HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary");
summaryInput.setValueAttribute("Spring Rocks");
HtmlTextArea textInput = createMsgFormPage.getHtmlElementById("text");
textInput.setText("In case you didn't know, Spring Rocks!");
HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "submit");
HtmlPage newMessagePage = submit.click();
val form = createMsgFormPage.getHtmlElementById("messageForm")
val summaryInput = createMsgFormPage.getHtmlElementById("summary")
summaryInput.setValueAttribute("Spring Rocks")
val textInput = createMsgFormPage.getHtmlElementById("text")
textInput.setText("In case you didn't know, Spring Rocks!")
val submit = form.getOneHtmlElementByAttribute("input", "type", "submit")
val newMessagePage = submit.click()
마지막으로 새 message가 성공적으로 생성되었는지 확인할 수 있습니다. 다음 assertions use the AssertJ library를 사용합니다:
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123");
String id = newMessagePage.getHtmlElementById("id").getTextContent();
assertThat(id).isEqualTo("123");
String summary = newMessagePage.getHtmlElementById("summary").getTextContent();
assertThat(summary).isEqualTo("Spring Rocks");
String text = newMessagePage.getHtmlElementById("text").getTextContent();
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!");
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123")
val id = newMessagePage.getHtmlElementById("id").getTextContent()
assertThat(id).isEqualTo("123")
val summary = newMessagePage.getHtmlElementById("summary").getTextContent()
assertThat(summary).isEqualTo("Spring Rocks")
val text = newMessagePage.getHtmlElementById("text").getTextContent()
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!")
앞의 코드는 여러 가지 방법으로 MockMvc test를 개선합니다. 첫째, 더 이상 form을 명시적으로 확인한 다음 form과 유사한 요청을 만들 필요가 없습니다. 대신 form을 요청하고 작성하고 제출하여 overhead를 크게 줄였습니다.
중요한 요소는 HtmlUnit이 Mozilla Rhino 엔진을 사용하여 JavaScript를 평가한다는 것입니다. 이는 페이지 내에서 JavaScript의 동작을 테스트 할 수도 있음을 의미합니다.
HtmlUnit 사용에 대한 추가 정보는 HtmlUnit documentation를 참조하십시오.
Advanced MockMvcWebClientBuilder
지금까지 예제에서 우리는 Spring TestContext Framework에 의해 로드된 WebApplicationContext
를 기반으로 WebClient
를 구축함으로써 가능한 가장 간단한 방법으로 MockMvcWebClientBuilder
를 사용했습니다.
이 접근 방식은 다음에서 반복됩니다:
WebClient webClient;
@BeforeEach
void setup(WebApplicationContext context) {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.build();
}
lateinit var webClient: WebClient
@BeforeEach
fun setup(context: WebApplicationContext) {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.build()
}
다음 예와 같이 추가 configuration option을 지정할 수도 있습니다:
WebClient webClient;
@BeforeEach
void setup() {
webClient = MockMvcWebClientBuilder
// demonstrates applying a MockMvcConfigurer (Spring Security)
.webAppContextSetup(context, springSecurity())
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
}
lateinit var webClient: WebClient
@BeforeEach
fun setup() {
webClient = MockMvcWebClientBuilder
// demonstrates applying a MockMvcConfigurer (Spring Security)
.webAppContextSetup(context, springSecurity())
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build()
}
또는 다음과 같이 MockMvc
instance를 별도로 구성하고 MockMvcWebClientBuilder
에 제공하여 정확히 동일한 설정을 수행할 수 있습니다:
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
webClient = MockMvcWebClientBuilder
.mockMvcSetup(mockMvc)
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
이것은 더 장황하지만 MockMvc
instance로 WebClient
를 구축하면 MockMvc의 모든 기능을 손쉽게 사용할 수 있습니다.
MockMvc instance 생성에 대한 자세한 내용은 Setup Choices을 참조하십시오.
|
MockMvc and WebDriver
이전 section에서는 raw HtmlUnit API와 함께 MockMvc를 사용하는 방법을 살펴 보았습니다. 이 section에서는 Selenium WebDriver 내에서 추가 추상화를 사용하여 작업을 더 쉽게 만듭니다.
Why WebDriver and MockMvc?
우리는 이미 HtmlUnit과 MockMvc를 사용할 수 있는데 왜 WebDriver를 사용하려고 합니까? Selenium WebDriver는 코드를 쉽게 구성 할 수 있는 매우 우아한 API를 제공합니다. 작동 방식을 더 잘 보여주기 위해 이 section에서 예제를 살펴봅니다.
Selenium의 일부 임에도 불구하고 WebDriver는 테스트를 실행하는 데 Selenium Server가 필요하지 않습니다. |
message가 제대로 생성되었는지 확인해야한다고 가정해 보겠습니다. 테스트에는 HTML form input element를 찾고, 채우고, 다양한 assertion을 하는 것이 포함됩니다.
이 접근 방식은 오류 조건도 테스트 하기를 원하기 때문에 수많은 개별 테스트를 수행합니다. 예를 들어, form의 일부만 작성하면 오류가 발생하도록 합니다. 전체 form을 작성하면 나중에 새로 생성된 message가 표시됩니다.
field 중 하나의 이름이 “summary” 인 경우 테스트의 여러 위치에서 다음과 유사한 내용이 반복 될 수 있습니다:
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
val summaryInput = currentPage.getHtmlElementById("summary")
summaryInput.setValueAttribute(summary)
그렇다면 id
를 smmry
로 변경하면 어떻게 될까요?
그렇게하면 이 변경 사항을 통합하기 위해 모든 테스트를 업데이트해야 합니다.
이것은 DRY 원칙을 위반하므로 다음과 같이 이 코드를 자체 method로 추출하는 것이 이상적입니다:
public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) {
setSummary(currentPage, summary);
// ...
}
public void setSummary(HtmlPage currentPage, String summary) {
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
}
fun createMessage(currentPage: HtmlPage, summary:String, text:String) :HtmlPage{
setSummary(currentPage, summary);
// ...
}
fun setSummary(currentPage:HtmlPage , summary: String) {
val summaryInput = currentPage.getHtmlElementById("summary")
summaryInput.setValueAttribute(summary)
}
이렇게하면 UI를 변경하더라도 모든 테스트를 업데이트 할 필요가 없습니다.
다음 예제와 같이 한 단계 더 나아가이 logic을 현재 사용중인 HtmlPage
를 나타내는 Object
내에 배치 할 수도 있습니다:
public class CreateMessagePage {
final HtmlPage currentPage;
final HtmlTextInput summaryInput;
final HtmlSubmitInput submit;
public CreateMessagePage(HtmlPage currentPage) {
this.currentPage = currentPage;
this.summaryInput = currentPage.getHtmlElementById("summary");
this.submit = currentPage.getHtmlElementById("submit");
}
public <T> T createMessage(String summary, String text) throws Exception {
setSummary(summary);
HtmlPage result = submit.click();
boolean error = CreateMessagePage.at(result);
return (T) (error ? new CreateMessagePage(result) : new ViewMessagePage(result));
}
public void setSummary(String summary) throws Exception {
summaryInput.setValueAttribute(summary);
}
public static boolean at(HtmlPage page) {
return "Create Message".equals(page.getTitleText());
}
}
class CreateMessagePage(private val currentPage: HtmlPage) {
val summaryInput: HtmlTextInput = currentPage.getHtmlElementById("summary")
val submit: HtmlSubmitInput = currentPage.getHtmlElementById("submit")
fun <T> createMessage(summary: String, text: String): T {
setSummary(summary)
val result = submit.click()
val error = at(result)
return (if (error) CreateMessagePage(result) else ViewMessagePage(result)) as T
}
fun setSummary(summary: String) {
summaryInput.setValueAttribute(summary)
}
fun at(page: HtmlPage): Boolean {
return "Create Message" == page.getTitleText()
}
}
}
이전에는 이 pattern을 Page Object Pattern이라고 했습니다. HtmlUnit으로 확실히 할 수 있지만 WebDriver는 이 패턴을 훨씬 쉽게 구현할 수 있도록 다음 section에서 살펴볼 몇 가지 도구를 제공합니다.
MockMvc and WebDriver Setup
Selenium WebDriver를 Spring MVC Test framework와 함께 사용하려면 프로젝트에 org.seleniumhq.selenium:selenium-htmlunit-driver
에 대한 test dependency가 포함되어 있는지 확인하십시오.
다음 예제와 같이 MockMvcHtmlUnitDriverBuilder
를 사용하여 MockMvc와 통합되는 Selenium WebDriver를 쉽게 만들 수 있습니다:
WebDriver driver;
@BeforeEach
void setup(WebApplicationContext context) {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build();
}
lateinit var driver: WebDriver
@BeforeEach
fun setup(context: WebApplicationContext) {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build()
}
이것은 MockMvcHtmlUnitDriverBuilder 를 사용하는 간단한 예입니다.
고급 사용법은 Advanced MockMvcHtmlUnitDriverBuilder 를 참조하세요.
|
앞의 예제는 서버로 localhost
를 참조하는 모든 URL이 실제 HTTP connection없이 MockMvc
instance로 전달되도록 합니다.
다른 모든 URL은 정상적으로 network connection을 사용하여 요청됩니다.
이를 통해 CDN 사용을 쉽게 테스트 할 수 있습니다.
MockMvc and WebDriver Usage
이제 우리는 평소처럼 WebDriver를 사용할 수 있지만 application을 Servlet container에 배포할 필요가 없습니다. 예를 들어, 다음과 같은 message를 생성하도록 view를 요청할 수 있습니다:
CreateMessagePage page = CreateMessagePage.to(driver);
val page = CreateMessagePage.to(driver)
그런 다음 form을 작성하고 제출하여 다음과 같이 message를 작성할 수 있습니다:
ViewMessagePage viewMessagePage =
page.createMessage(ViewMessagePage.class, expectedSummary, expectedText);
val viewMessagePage =
page.createMessage(ViewMessagePage::class, expectedSummary, expectedText)
이것은 Page Object Pattern을 활용하여 HtmlUnit test의 디자인을 향상시킵니다.
Why WebDriver and MockMvc?에서 언급했듯이 HtmlUnit과 함께 Page Object Pattern을 사용할 수 있지만 WebDriver를 사용하면 훨씬 더 쉽습니다.
A다음 CreateMessagePage
구현을 고려하십시오:
public class CreateMessagePage
extends AbstractPage { (1)
(2)
private WebElement summary;
private WebElement text;
(3)
@FindBy(css = "input[type=submit]")
private WebElement submit;
public CreateMessagePage(WebDriver driver) {
super(driver);
}
public <T> T createMessage(Class<T> resultPage, String summary, String details) {
this.summary.sendKeys(summary);
this.text.sendKeys(details);
this.submit.click();
return PageFactory.initElements(driver, resultPage);
}
public static CreateMessagePage to(WebDriver driver) {
driver.get("http://localhost:9990/mail/messages/form");
return PageFactory.initElements(driver, CreateMessagePage.class);
}
}
1 | CreateMessagePage 는 AbstractPage 를 확장합니다.
우리는 AbstractPage 의 세부 사항을 다루지 않지만 요약하면 모든 페이지에 대한 공통 기능을 포함합니다.
예를 들어 application에 navigational bar, global error message 및 기타 특징을 가지고 있는 경우 이 logic을 shared location에 배치할 수 있습니다. |
2 | 우리는 우리가 관심 있는 HTML 페이지의 각 부분에 대한 member variable를 가지고 있습니다.
WebElement 유형입니다.
WebDriver의 PageFactory 를 사용하면 각 WebElement를 자동으로 해결하여 CreateMessagePage 의 HtmlUnit 버전에서 많은 코드를 제거할 수 있습니다.
PageFactory#initElements(WebDriver,Class<T>) method는 field name을 사용하여 HTML 페이지 내에서 요소의 id 또는 name 으로 검색함으로써 각 WebElement 를 자동으로 해결합니다. |
3 | @FindBy annotation을 사용하여 default lookup behavior를 override할 수 있습니다.
예제에서는 @FindBy annotation을 사용하여 css selector (input[type=submit])로 submit button을 찾는 방법을 보여줍니다. |
class CreateMessagePage(private val driver: WebDriver) : AbstractPage(driver) { (1)
(2)
private lateinit var summary: WebElement
private lateinit var text: WebElement
(3)
@FindBy(css = "input[type=submit]")
private lateinit var submit: WebElement
fun <T> createMessage(resultPage: Class<T>, summary: String, details: String): T {
this.summary.sendKeys(summary)
text.sendKeys(details)
submit.click()
return PageFactory.initElements(driver, resultPage)
}
companion object {
fun to(driver: WebDriver): CreateMessagePage {
driver.get("http://localhost:9990/mail/messages/form")
return PageFactory.initElements(driver, CreateMessagePage::class.java)
}
}
}
1 | CreateMessagePage 는 AbstractPage 를 확장합니다.
우리는 AbstractPage 의 세부 사항을 다루지 않지만 요약하면 모든 페이지에 대한 공통 기능을 포함합니다.
예를 들어 application에 navigational bar, global error message 및 기타 특징을 가지고 있는 경우 이 logic을 shared location에 배치할 수 있습니다. |
2 | 우리는 우리가 관심 있는 HTML 페이지의 각 부분에 대한 member variable를 가지고 있습니다.
WebElement 유형입니다.
WebDriver의 PageFactory 를 사용하면 각 WebElement를 자동으로 해결하여 CreateMessagePage 의 HtmlUnit 버전에서 많은 코드를 제거할 수 있습니다.
PageFactory#initElements(WebDriver,Class<T>) method는 field name을 사용하여 HTML 페이지 내에서 요소의 id 또는 name 으로 검색함으로써 각 WebElement 를 자동으로 해결합니다. |
3 | @FindBy annotation을 사용하여 default lookup behavior를 override할 수 있습니다.
예제에서는 @FindBy annotation을 사용하여 css selector (input[type=submit])로 submit button을 찾는 방법을 보여줍니다. |
마지막으로 새 messag가 성공적으로 생성되었는지 확인할 수 있습니다. 다음 assertion은 AssertJ assertion library를 사용합니다:
assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");
assertThat(viewMessagePage.message).isEqualTo(expectedMessage)
assertThat(viewMessagePage.success).isEqualTo("Successfully created a new message")
ViewMessagePage
를 통해 custom domain model과 상호 작용할 수 있음을 알 수 있습니다.
예를 들어 Message
object를 반환하는 method를 노출합니다:
public Message getMessage() throws ParseException {
Message message = new Message();
message.setId(getId());
message.setCreated(getCreated());
message.setSummary(getSummary());
message.setText(getText());
return message;
}
fun getMessage() = Message(getId(), getCreated(), getSummary(), getText())
그런 다음 assertion에서 rich domain object를 사용할 수 있습니다.
마지막으로, 테스트가 완료되면 다음과 같이 WebDriver
instance를 닫는 것을 잊지 마십시오:
@AfterEach
void destroy() {
if (driver != null) {
driver.close();
}
}
@AfterEach
fun destroy() {
if (driver != null) {
driver.close()
}
}
WebDriver 사용에 대한 추가 정보는 Selenium WebDriver documentation를 참조하십시오.
Advanced MockMvcHtmlUnitDriverBuilder
지금까지 예제에서 우리는 Spring TestContext Framework에 의해 로드된 WebApplicationContext
를 기반으로 WebDriver
를 구축함으로써 가능한 가장 간단한 방법으로 MockMvcHtmlUnitDriverBuilder
를 사용했습니다.
이 접근 방식은 다음과 같이 여기에서 반복됩니다:
WebDriver driver;
@BeforeEach
void setup(WebApplicationContext context) {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build();
}
lateinit var driver: WebDriver
@BeforeEach
fun setup(context: WebApplicationContext) {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build()
}
다음과 같이 추가 configuration option을 지정할 수도 있습니다:
WebDriver driver;
@BeforeEach
void setup() {
driver = MockMvcHtmlUnitDriverBuilder
// demonstrates applying a MockMvcConfigurer (Spring Security)
.webAppContextSetup(context, springSecurity())
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
}
lateinit var driver: WebDriver
@BeforeEach
fun setup() {
driver = MockMvcHtmlUnitDriverBuilder
// demonstrates applying a MockMvcConfigurer (Spring Security)
.webAppContextSetup(context, springSecurity())
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build()
}
또는 다음과 같이 MockMvc
instance를 별도로 구성하고 MockMvcHtmlUnitDriverBuilder
에 제공하여 정확히 동일한 설정을 수행할 수 있습니다:
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
driver = MockMvcHtmlUnitDriverBuilder
.mockMvcSetup(mockMvc)
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed
이것은 더 장황하지만 MockMvc
instance로 WebDriver
를 빌드하면 MockMvc의 모든 기능을 쉽게 사용할 수 있습니다.
MockMvc instance 생성에 대한 자세한 내용은 Setup Choices을 참조하십시오.
|
MockMvc and Geb
이전 section에서 WebDriver와 함께 MockMvc를 사용하는 방법을 보았습니다. 이 section에서는 Geb를 사용하여 테스트를 훨씬 더 멋지게 만듭니다.
Why Geb and MockMvc?
Geb는 WebDriver의 지원을 받으므로 WebDriver에서 얻는 것과 동일한 많은 이점을 제공합니다. 그러나 Geb는 우리를 위해 일부 상용구 코드를 처리하여 작업을 훨씬 더 쉽게 만듭니다
MockMvc and Geb Setup
다음과 같이 MockMvc를 사용하는 Selenium WebDriver로 Geb `Browser`를 쉽게 초기화 할 수 있습니다:
def setup() {
browser.driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build()
}
이것은 MockMvcHtmlUnitDriverBuilder 를 사용하는 간단한 예입니다.
고급 사용법은 Advanced MockMvcHtmlUnitDriverBuilder 를 참조하세요.
|
이렇게하면 실제 HTTP connection없이 localhost
를 server로 참조하는 모든 URL이 MockMvc
instance로 전달됩니다.
다른 URL은 정상적으로 network connection을 사용하여 요청됩니다.
이를 통해 CDN 사용을 쉽게 테스트 할 수 있습니다.
MockMvc and Geb Usage
이제 우리는 평소처럼 Geb를 사용할 수 있지만 애플리케이션을 Servlet container에 배포 할 필요가 없습니다. 예를 들어, 다음과 같은 message를 생성하도록 view를 요청할 수 있습니다:
to CreateMessagePage
그런 다음 form을 작성하고 submit하여 다음과 같이 message지를 작성할 수 있습니다:
when:
form.summary = expectedSummary
form.text = expectedMessage
submit.click(ViewMessagePage)
인식되지 않는 method 호출, property accesse 또는 찾을 수 없는 참조는 current page object로 전달됩니다. 이것은 WebDriver를 직접 사용할 때 필요한 많은 상용구 코드를 제거합니다.
직접적인 WebDriver 사용과 마찬가지로 Page Object Pattern을 사용하여 HtmlUnit test의 디자인을 개선합니다.
앞서 언급했듯이 HtmlUnit 및 WebDriver에서 Page Object Pattern을 사용할 수 있지만 Geb에서는 훨씬 더 쉽습니다.
새로운 Groovy 기반 CreateMessagePage
구현을 고려하십시오:
class CreateMessagePage extends Page {
static url = 'messages/form'
static at = { assert title == 'Messages : Create'; true }
static content = {
submit { $('input[type=submit]') }
form { $('form') }
errors(required:false) { $('label.error, .alert-error')?.text() }
}
}
CreateMessagePage
는 Page
를 확장합니다.
Page
의 세부 사항은 다루지 않지만 요약하면 모든 페이지에 대한 공통 기능이 포함되어 있습니다.
이 페이지를 찾을 수 있는 URL을 정의합니다.
이렇게하면 다음과 같이 페이지로 이동할 수 있습니다:
to CreateMessagePage
지정된 페이지에 있는지 여부를 결정하는 at
closure도 있습니다.
올바른 페이지에 있으면 true를 반환해야합니다.
이것이 우리가 다음과 같이 올바른 페이지에 있다고 주장 할 수있는 이유입니다:
then:
at CreateMessagePage
errors.contains('This field is required.')
closure에 assertion을 사용하여 잘못된 페이지에 있는 경우 어디에서 잘못되었는지 확인할 수 있습니다. |
다음으로 페이지 내의 모든 관심 영역을 지정하는 content
closure를 만듭니다.
jQuery-ish Navigator API를 사용하여 관심있는 콘텐츠를 선택할 수 있습니다.
마지막으로 다음과 같이 새 message가 성공적으로 생성되었는지 확인할 수 있습니다:
then:
at ViewMessagePage
success == 'Successfully created a new message'
id
date
summary == expectedSummary
message == expectedMessage
Geb를 최대한 활용하는 방법에 대한 자세한 내용은 The Book of Geb 사용자 설명서를 참조하세요.
Testing Client Applications
client-side 테스트를 사용하여 내부적으로 RestTemplate
을 사용하는 코드를 테스트 할 수 있습니다.
아이디어는 예상되는 요청을 선언하고 “stub” responses를 제공하여 격리된 상태에서 (즉, 서버를 실행하지 않고) 코드 테스트에 집중할수 있도록하는 것입니다.
다음 예는 이를 수행하는 방법을 보여줍니다:
RestTemplate restTemplate = new RestTemplate();
MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess());
// Test code that uses the above RestTemplate ...
mockServer.verify();
val restTemplate = RestTemplate()
val mockServer = MockRestServiceServer.bindTo(restTemplate).build()
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess())
// Test code that uses the above RestTemplate ...
mockServer.verify()
앞의 예에서 MockRestServiceServer
(client-side REST 테스트를 위한 central class)는 실제 요청을 expectation에 대해 assert하고 " “stub” responses를 반환하는 custom ClientHttpRequestFactory
로 RestTemplate
을 구성합니다.
이 경우 /greeting
에 대한 요청을 예상하고 text/plain
content가 포함 된 200 response를 반환하려고합니다.
필요에 따라 추가 예상 request 및 stub response를 정의 할 수 있습니다.
예상되는 requst와 stub response를 정의 할 때 RestTemplate
은 평소와 같이 client-side 코드에서 사용할 수 있습니다.
테스트가 끝나면 mockServer.verify()
를 사용하여 모든 expectation이 충족되었는지 확인할 수 있습니다.
기본적으로 request는 expectation이 선언된 순서대로 예상됩니다.
서버를 빌드 할 때 ignoreExpectOrder
옵션을 설정할 수 있습니다.
이 경우 모든 expectation이 (순서대로) 확인되어 주어진 요청에 대한 일치 항목을 찾습니다.
이는 요청이 어떤 순서로든 올 수 있음을 의미합니다.
다음 예제에서는 ignoreExpectOrder
를 사용합니다:
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build()
기본적으로 순서가 지정되지 않은 요청이 있더라도 각 요청은 한 번만 실행할 수 있습니다.
expect
method는 count range(예 : once
, manyTimes
, max
, min
, between
등)를 지정하는 ExpectedCount
argument를 허용하는 overloaded variant를 제공합니다.
다음 예제에서는 times
를 사용합니다:
RestTemplate restTemplate = new RestTemplate();
MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess());
mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess());
// ...
mockServer.verify();
val restTemplate = RestTemplate()
val mockServer = MockRestServiceServer.bindTo(restTemplate).build()
mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess())
mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess())
// ...
mockServer.verify()
ignoreExpectOrder
가 설정되지 않았고(기본값) 따라서 요청이 선언 순서대로 예상되는 경우 해당 순서는 예상되는 모든 요청 중 첫 번째에만 적용됩니다.
예를 들어 "/something" 이 두 번 예상되고 "/somewhere" 가 세 번 나오는 경우 "/somewhere" 에 대한 요청이 있기 전에 "/something" 에 대한 요청이 있어야 하지만 후속 "/something" 및 "/somewhere", 요청은 언제든지 올 수 있습니다.
위의 모든 사항에 대한 대안으로 client-side 테스트 지원은 MockMvc
instance에 binding하기 위해 RestTemplate`으로 구성할 수 있는 `ClientHttpRequestFactory
구현도 제공합니다.
를 통해 서버를 실행하지 않고 실제 server-side logic을 사용하여 요청을 처리 할 수 있습니다.
다음 예는 이를 수행하는 방법을 보여줍니다:
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc));
// Test code that uses the above RestTemplate ...
val mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build()
restTemplate = RestTemplate(MockMvcClientHttpRequestFactory(mockMvc))
// Test code that uses the above RestTemplate ...
Static Imports
server-side 테스트와 마찬가지로 client-side 테스트를 위한 유창한 API에는 몇 가지 static import가 필요합니다.
MockRest*
를 검색하면 쉽게 찾을 수 있습니다.
Eclipse 사용자는 Java → Editor → Content Assist → Favorites 아래의 Eclipse preferences에서 MockRestRequestMatchers.*
및 MockRestResponseCreators.*
를 “favorite static members” 로 추가해야합니다.
static method name의 첫 문자를 입력 한 후 content assist를 사용할 수 있습니다.
다른 IDE(예: IntelliJ)는 추가 구성이 필요하지 않을 수 있습니다.
static members에 대한 코드 완성 지원을 확인하십시오.
Further Examples of Client-side REST Tests
Spring MVC Test의 자체 테스트에는 client-side REST test의 example tests가 포함됩니다
Further Resources
테스트에 대한 자세한 내용은 다음 resource를 참조하십시오:
-
JUnit: "Java 용 프로그래머 친화적 인 테스트 프레임 워크". Spring Framework가 test suite에서 사용하고 <testcontext-framework, Spring TestContext Framework>>에서 지원됩니다.
-
TestNG: test groups, data-driven testing, distributed testing 및 기타 기능에 대한 지원이 추가된 JUnit에서 영감을 얻은 test framework입니다. Spring TestContext Framework에서 지원
-
AssertJ: Java 8 lambdas, streams 및 기타 기능에 대한 지원을 포함한 “자바에 대한 풍부한 assertion”.
-
Mock Objects: Wikipedia의 Article
-
MockObjects.com:test-driven development 내에서 코드 디자인을 개선하기 위한 기술인 mock object 전용 웹 사이트입니다.
-
Mockito: Test Spy pattern을 기반으로 한 Java mock library. test suite의 Spring Framework에서 사용됩니다
-
EasyMock: Java library “Java의 proxy 메커니즘을 사용하여 즉석에서 생성하여 interface (및 class extension을 통해 object)에 대한 Mock Object를 제공합니다.”
-
JMock: mock object로 Java 코드의 test-driven development을 지원하는 라이브러리입니다.
-
DbUnit: JUnit extension (Ant 및 Maven에서도 사용 가능)은 데이터베이스 기반 프로젝트를 대상으로하며 무엇보다도 테스트 실행 사이에 데이터베이스를 알려진 state로 만듭니다.
-
Testcontainers: JUnit 테스트를 지원하고 공통 데이터베이스, Selenium 웹 브라우저 또는 Docker container에서 실행할 수 있는 모든 것의 lightweight의 일회용 instance를 제공하는 Java library입니다.
-
The Grinder: Java 부하 test framework.
-
SpringMockK: Mockito 대신 MockK를 사용하여 Kotlin으로 작성된 Spring Boot 통합 테스트를 지원합니다.