Spring/Spring Boot

[Spring Boot] 2-8. Core Features: Test

noahkim_ 2023. 10. 11. 02:01

Spring Boot는 spring-boot-starter-test 모듈을 통해 test를 위한 어노테이션과 유틸클래스, 테스트 라이브러리를 제공합니다.

 

1. Test Scope Dependencies

  • spring-boot-test : test core feature
  • spring-boot-test-autoconfigure : test auto-configuration
  • assertj-core : 표현식을 가진 단언 라이브러리
  • hamcrest : 매처 라이브러리
  • junit-jupiter : junit5 핵심 모듈. (unit test 프레임워크)
  • mockito-core, mockito-junit-jupiter : mocking, stubbing 프레임워크
  • JsonPath : JSON을 위한 XPath 모듈

 

2. Testing Spring Applications

  • Spring의 핵심기능인 DI는 Unit test를 더욱 쉽게 만들어줍니다.
    • 기본적으로 객체 생성은 new 키워드를 사용합니다.
    • 이때 객체의 의존성은 생성자의 매개변수로 주입됩니다.
    • DI를 사용할 경우, Spring Framework에서는 실제 의존성 대신 mock object를 주입해줄 수 있습니다.
  • Integration test를 위한 테스트 환경 구성을 돕습니다
    • Integration test는 ApplicationContext를 사용하며 테스트 환경은 실제 외부 Infrastructure를 사용하지 않습니다.
    • 외부 Infrastructure를 mocking 할 수 있어, Integration test의 실행과 검증이 간소화됩니다.

 

3. Testing Spring Boot Applications

@SpringBootTest

  • Test 환경의 자동 구성을 지원합니다.
    • 표준으로 사용되는 spring-test의 @ContextConfiguration을 완벽하게 대체하며, 추가적으로 유용한 기능을 제공합니다
    • 테스트 시 필요한 빈들과 설정을 자동으로 구성하기 위한 ApplicationContext를 초기화합니다.

 

  • server 실행 관련 설정이 가능합니다.
    • 기본적으로 server 실행 하지 않습니다.
    • webEnvironment attribute를 통해 설정이 가능합니다. 

 

(default) MOCK
  • mock 설정이 적용된 ApplicationContext를 로딩합니다.
  • Embedded server는 실행되지 않습니다.
  • fall back 에서 복구를 위해 사용되는 설정입니다.
    • mock설정이 모듈의 부재로 인해 실패할 경우, non-web ApplicationContext를 생성합니다.
  • @AutoConfigureMockMvc 혹은 @AutoConfigureWebTestClient에 의해 자동 설정됩니다. 

 

RANDOM_PORT
  • 실제 설정이 적용된 WebServerApplicationContext를 로딩합니다.
  • 임의의 포트로 Embedded server가 실행됩니다.

 

DEFINED_PORT
  • 실제 설정이 적용된 WebServerApplicationContext를 로딩합니다.
  • 설정된 포트로 Embedded server가 실행됩니다.
    • property 파일의 server.port로 전달됩니다. (기본값:8080)

 

NONE
  • 아무 설정이 되지 않은 ApplicationContext를 로딩합니다.
    • SpringApplication에 의해 로딩됩니다.

 

Detecting Test Configuration

  • @*Test 애노테이션은 primary 설정클래스를 자동적으로 검색합니다.
    • 테스트가 실행되는 클래스로부터 @SpringBootApplication (혹은 @SpringBootConfiguration)이 붙은 클래스를 찾습니다.
  • Slicing Test를 위한 기능을 제공합니다.
    • @SpringBootApplication의 exclude attribute를 사용하여 component scan 대상을 제외할 수 있습니다.
    • @TestConfiguration이 붙은 테스트용 설정클래스를 사용하여 테스트 전용 설정환경을 커스터마이징할 수 있습니다.
      • @TestConfiguration는 중첩클래스 형태로 작성할 수 있습니다.
      • @Configuration의 중첩클래스와 다르게 primary configuration의 보충 및 추가 용도로 사용됩니다.
  • ApplicationContext은 초기에 한번 로드되며 test간 ApplicationContext는 공유됩니다.

 

Using the Test Configuration Main Method

  • @SpringBootTest는 보통 가장 먼저 primary 설정 클래스로 @SpringBootApplication을 찾게 됩니다.
    • @SpringBootApplication는 SpringApplication 셋팅 및 실행하는 main 메서드를 가진 클래스에 붙은 어노테이션입니다.
    • SpringApplication의 셋팅을 통해, ApplicationContext를 커스터마이징 할 수 있습니다.
  • @SpringBootTest는 primary 설정 클래스의 main 메서드를 호출하지 않고 설정클래스를 읽어 ApplicationContext 설정합니다.
    • useMainMethod attribute 속성을 통해 ApplicationContext 설정 방식을 설정할 수 있습니다.

 

ALWAYS
  • main 메소드 호출을 처리 로직에 추가하여 ApplicationContext를 설정합니다.

 

WHEN_AVAILABLE
  • 기본적으로 main 메소드 호출하여 ApplicationContext를 설정합니다.
  • 만약 main 메서드가 없어 호출하지 못하면 기본 매커니즘으로 ApplicationContext를 설정합니다

 

Excluding Test Configuration

  • @TestConfiguration은 내부 클래스로 사용되거나 상위 수준 클래스로 사용됩니다.
    • 상위 수준 클래스에 붙이면, src/test/java 의 클래스를 자동으로 스캔하지 않도록 합니다.
  • @Import를 통해 @TestConfiguration를 적용할 수 있습니다.
  • @ComponentScan으로 설정 클래스를 스캔하여 적용할 수 있습니다.
    • 제외하고자 하는 설정 클래스는 excludeFilters 속성에 TypeExcludeFilter 클래스를 활용하여 제외할 수 있습니다.

 

Testing With a Mock Environment

  • Spring MVC를 MockMvc (or WebTestClient)를 가지고 mocking 하여 테스트할 수 있습니다.
  • mocking은 Spring MVC 에만 일어나므로 low-level인 servlet container는 mocking 되지 않습니다.

 

Testing With a Running Server

  • 테스트 환경에서 running server를 띄우고자 할 경우, RANDOM_PORT 사용이 권장됩니다.
  • @LocalServerPort를 통해 현재 떠 있는 서버의 포트를 주입받을 수 있습니다.

 

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortWebTestClientTests {

    @Test
    void exampleTest(@Autowired WebTestClient webClient) {
        webClient
            .get().uri("/")
            .exchange()
            .expectStatus().isOk()
            .expectBody(String.class).isEqualTo("Hello World");
    }
}
  • WebTestClient를 통해 테스트 중에 요청과 응답을 연속적으로 할 수 있는 API 제공합니다   
    • 서버 요청 및 응답이 편리해집니다.
    • ApplicationContext에 주입받아 사용할 수 있습니다.

 

Customizing WebTestClient

  • WebTestClientBuilderCustomizer를 재정의하고 빈으로 등록하여, WebTestClient를 커스터마이징 할 수 있습니다.

 

Mocking and Spying Beans

  • 테스트 환경에서 Bean을 Mocking 하는 경우가 있습니다.

 

@MockBean
  • @MockBean 어노테이션을 붙여 bean을 Mockito mock로 정의할 수 있습니다.
    • class-level : 새로운 mock bean이 등록됩니다.
    • field-level : 존재하는 bean의 definition을 mock bean의 definition 교체하여 주입합니다.
  • 테스트 후 Mock Bean의 definition은 원래의 bean definition으로 자동적으로 교체됩니다.
    • 기본적으로 ApplicationContext는 test간 공유됩니다
    • 테스트 후  Mock Bean은 원래의 bean 으로 리셋되므로 test마다 ApplicationContext가 refresh 됩니다
      • 이로 인해 테스트 성능이 낮아질 수 있습니다.
  • Mock Bean은 Bean 초기화 로직이 적용됩니다.
    • Bean 초기화 후 definition을 교체하는 과정으로 생성되기 때문입니다.
  • CGLib 라이브러리를 통해 Proxy객체로 생성되면 final 메서드는 mocking 할 수 없습니다.
    • inline mock maker를 사용하여 Mockito를 설정할 경우 허가될 수 있습니다.

 

@SpyBean
  • 특정 메서드의 동작을 mocking하고 mocking 되지 않은 메서드는 그대로 동작하는 Bean.

 

AopTestUtils.getTargetObject(proxySpy)
  • Bean이 Proxy 기반으로 동작할 경우 기대치 설정 시에 Proxy를 제거해야 합니다.
  • Proxy Bean으로부터 실제 Bean을 가져옵니다.
  • 캐싱
    • SpyBean의 메서드 중, @Cacheable 메서드가 있다면 애플리케이션 실행 시 -parameters 옵션을 사용합니다.
      • 컴파일 타임 : @Cacheable 메서드의 parameter 이름은 Bean class의 class파일에 저장됩니다.
      • 런타임 : class 파일에 접근하여 parameter이름을 얻어 리플렉션을 통해 parameter 변수의 값을 얻습니다.
    • parameter를 key로 한 cache 값은 애플리케이션에서 사용중인 cache infrastructure에 보관됩니다.

 

Auto-configured Test

  • auto-configuration 기술은 편리하지만 때로는 테스트 환경보다 더 많은 설정관련 동작을 수행합니다.
  • 테스트 환경에 딱 알맞는 자동설정만 할 수 있도록 test slice 기능을 제공합니다.

 

@*Test
// ...
@AutoConfigureCache
@AutoConfigureDataJdbc
@AutoConfigureTestDatabase
@ImportAutoConfiguration
public @interface DataJdbcTest {
  • spring-boot-test-autoconfigure 모듈은 test slice를 자동적으로 설정하기 위한 어노테이션들을 제공합니다.
    • @*Test 의 패턴의 이름을 가집니다.
    • 메타 어노테이션 입니다. (@AutoConfigure*, @ImportAutoConfiguration)
    • 적절한 컴포넌트를 스캔하여 모듈에 맞는 자동설정을 동작시키고 ApplicationContext를 로드합니다.
  • @*Test 어노테이션은 함께 사용되는 것을 지원하지 않습니다.
    • multiple slice를 구성하고자 할 경우, @*Test와 @AutoConfigure*를 조합해서 사용하세요.

 

@AutoConfigure*
// ...
@ImportAutoConfiguration
public @interface AutoConfigureDataJdbc {
  • 특정 모듈의 자동 설정을 동작하도록 하는 어노테이션 입니다.
  • @AutoConfigure*는 @SpringBootTest와도 사용할 수 있습니다.

 

@ImportAutoConfiguration
// ...
@Import(ImportAutoConfigurationImportSelector.class)
public @interface ImportAutoConfiguration {
  • @AutoConfiguration* 을 import하는 어노테이션 입니다.
  • ImportAutoConfigurationImportSelector 대상 @AutoConfiguration*가 ImportCandidates에 보관됩니다. 
    • 설정할 수 있는 자동설정 클래스는 ImportCandidates의 *AutoConfiguration set으로 제한됩니다.
    • include, exclude 속성도 ImportCandidates 내의 *AutoConfiguration 클래스만 유효하게 동작합니다.

 

Auto-configured JSON Tests

@JsonTest
// ... 
@AutoConfigureCache
@AutoConfigureJson
@AutoConfigureJsonTesters
@ImportAutoConfiguration
public @interface JsonTest {
  • JSON 오브젝트를 직렬화 / 역직렬화하는데 사용되는 자동설정 기능을 제공합니다.
  • Jackson, Gson, Jsonb 라이브러리를 포함하여 다양하게 제공합니다.

 

@JsonTest
class MyJsonTests {

    @Autowired
    private JacksonTester<VehicleDetails> json;

    @Test
    void serialize() throws Exception {
        VehicleDetails details = new VehicleDetails("Honda", "Civic");
        assertThat(this.json.write(details)).isEqualToJson("expected.json");
        assertThat(this.json.write(details)).hasJsonPathStringValue("@.make");
        assertThat(this.json.write(details)).extractingJsonPathStringValue("@.make").isEqualTo("Honda");
    }

    @Test
    void deserialize() throws Exception {
        String content = "{\"make\":\"Ford\",\"model\":\"Focus\"}";
        assertThat(this.json.parse(content)).isEqualTo(new VehicleDetails("Ford", "Focus"));
        assertThat(this.json.parseObject(content).getMake()).isEqualTo("Ford");
    }
}
  • Support 클래스를 제공합니다 (JacksonTest, GsonTester, JsonbTester)

 

@Test
void someTest() throws Exception {
    SomeObject value = new SomeObject(0.152f);
    assertThat(this.json.write(value)).extractingJsonPathNumberValue("@.test.numberValue")
        .satisfies((number) -> assertThat(number.floatValue()).isCloseTo(0.15f, within(0.01f)));
}
  • AssertJ-based helper 라이브러리를 제공합니다. (JSONAssert, JsonPath)
  • satisfies : 값이 주어진 조건과 매칭되는지 단언하는 API 입니다.

 

Auto-configured Spring MVC Tests

@WebMvcTest
@AutoConfigureCache
@AutoConfigureWebMvc
@AutoConfigureMockMvc
@ImportAutoConfiguration
public @interface WebMvcTest {
  • Spring MVC Controller의 자동설정을 제공합니다.
  • Spring MVC infrastructure를 auto-configure합니다
    • @Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverter, Filter, HandlerInterceptor, WebMvcConfigurer, WebMvcRegistrations, HandlerMethodArgumentResolver 
    • (@Component, @ComfigurationProperties 빈들은 스캔되지 않습니다)

 

MockMvc
@WebMvcTest(UserVehicleController.class)
class MyControllerTests {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private UserVehicleService userVehicleService;

    @Test
    void testExample() throws Exception {
        given(this.userVehicleService.getVehicleDetails("sboot"))
            .willReturn(new VehicleDetails("Honda", "Civic"));
        this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
            .andExpect(status().isOk())
            .andExpect(content().string("Honda Civic"));
    }
}
  • Spring MVC의 테스팅을 지원하는 프레임워크 입니다.
  • Http Server를 띄우지 않고 Http 요청을 dispatch하고 결과를 검증할 수 있습니다.
  • 주로 컨트롤러와 뷰를 테스트하는데 사용됩니다.

 

@AutoConfigurerMockMvc
// ...
@ImportAutoConfiguration
@PropertyMapping("spring.test.mockmvc")
public @interface AutoConfigureMockMvc {
  • MockMvc를 auto-configure합니다.
  • MockMvc 자동설정을 커스터마이징 할 경우, @AutoConfigurerMockMvc의 attribute를 셋팅하세요.
    • example) 필터 추가, webclient 사용 등

 

@WithMockUser (Spring Security)
  • Spring Security를 사용하면 기본적으로 WebSercurityConfigurer를 스캔합니다.
  • 간단한 설정으로 테스트에 주요 기능만 테스트하도록 인증 어노테이션을 지원합니다.

 

Auto-configured Data JPA Tests

@DataJpaTest
  • JPA 테스트 관련 auto-configuration 기능을 제공합니다.
    • @Entity 클래스 스캔
    • Spring Data JPA Repository를 자동설정 합니다.
    • (embedded database 사용시 자동설정)
    • SQL query log 셋팅
      • (attribute) showSql 
      • (property) spring.jpa.show-sql 
  • @Component, @ComfigurationProperties 빈들은 스캔되지 않습니다

 

Transaction
  • data JPA 의 각 test는 기본적으로 transcational 하여 테스트 블록을 모두 실행한 후 롤백됩니다.

 

@Transactional(propagataion = Propagation.NOT_SUPPORTED)
  • transaction 사용을 끌 수 있습니다

 

TestEntityManager
  • 테스트용 EntityManager 입니다.
  • @AutoConfigureTestEntityManager를 붙여서 auto-configure를 동작시킬 수 있습니다.

 

@AutoConfigureTestDatabase
  • 테스트중에 사용되는 database를 자동설정하는 어노테이션 입니다.
  • replace attribute를 사용하여 테스트에 연결할 database를 지정할 수 있습니다.
    • (default) ANY : 테스트용 datasource로 교체합니다. (default: in-memory)
    • NONE : 교체하지 않습니다. (기존에 사용하던 datasource 사용)
    • AUTO_CONFIGURE : auto-configure 기능으로 만들어진 datasource로 교체합니다.

 

 

 

참고