Spring Boot Slice Test

이주오·2022년 1월 12일
0

spring

목록 보기
1/4

Spring Data JPA 사용하는 미션에서 멘토님께 리뷰를 받으며 다음과 같은 피드백을 받으며 슬라이스 테스트 존재에 대해 알게 되었다.

  • Repository Test시 @SpringBootTest를 @DataJpaTest로 변경해서 테스트 작성하기

슬라이스 테스트란 무엇이고 왜 사용하는 것일까??


Spring Boot 슬라이스 테스트


슬라이스 테스트란?

💡 Test slicing is about segmenting the `ApplicationContext` that is created for your test. Typically, if you want to test a controller using `MockMvc`, surely you don’t want to bother with the data layer. Instead you’d probably want to *mock* the service that your controller uses and validate that all the web-related interaction works as expected.
  • 즉 스프링은 레이어 별로 잘라서 특정 레이어에 대해서 Bean을 최소한으로 등록시켜 테스트 하고자 하는 부분에 최대한 단위 테스트를 지원해주고 있다.
  • 그렇다면 @SpringBootTest 대신 슬라이스 테스트를 하는 이유는 무엇일까??

F.I.R.S.T 테스트 원칙

단위 테스트는 응용 프로그램에서 테스트 가능한 가장 작은 소프트웨어를 실행하여 예상대로 동작하는지 확인하는 테스트이며 로버트 마틴의 클린코드에서 깨끗한 테스트를 위한 다섯 가지 F.I.R.S.T 규칙을 말한다.

  • Fast — 테스트는 빨라야 한다.
  • Isolated — 각 테스트는 서로 의존하면 안된다.
  • Repeatable — 테스트는 어떤 환경에서도 반복 가능해야 한다.
  • Self-validating — 테스트는 bool 값으로 결과를 내야 한다.
  • Timely — 테스트는 적시에 작성해야 한다.

@SpringBootTest 어노테이션을 사용하는 경우 단점은 아래와 같다.

  • 실제 구동되는 애플리케이션의 설정, 모든 Bean을 로드하기 때문에 시간이 오래걸리고 무겁다.
  • 테스트 단위가 크기 때문에 디버깅이 어려운 편이다.
  • 외부 API 콜같은 Rollback 처리가 안되는 테스트 진행을 하기 어려움

따라서 repository 레이어의 단위테스트의 경우 @SpringBootTest 대신 @DataJpaTest 사용하여 테스트를 작성하는 경우 통해 속도적인 측면과 의존성 측면에서 이점을 가질 수 있다.


슬라이스 테스트 어노테이션 종류

아래는 대표적인 슬라이스 테스트 어노테이션이 존재하는데 해당 글에서는 중 @WebMvcTest, @DataJpaTest 살펴보도록 할 것이다.

  • @WebMvcTest
  • @WebFluxTest
  • @DataJpaTest
  • @JsonTest
  • @RestClientTest

@WebMvcTest

  • MVC를 위한 테스트.
  • 웹에서 테스트하기 힘든 컨트롤러를 테스트하는 데 적합.
  • 웹상에서 요청과 응답에 대해 테스트할 수 있음.
  • 시큐리티, 필터까지 자동으로 테스트하며, 수동으로 추가/삭제 가능.
  • @SpringBootTest 어노테이션보다 가볍게 테스트할 수 있음.
  • 다음과 같은 내용만 스캔하도록 제한함.@Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverter, Filter, HandlerInterceptor,
    • 따라서 의존성이 끊기기 때문에, 예를 들면 서비스와 같은 객체들은 @MockBean을 사용해서 만들어 사용해야 한다.
@WebMvcTest(ShelterPostController.class)
public class ShelterPostControllerTest {

    @Autowired
    protected MockMvc mockMvc;

    @Autowired
    protected ObjectMapper objectMapper;

    @MockBean
    protected ShelterPostService shelterPostService;

    @Test
    @DisplayName("게시글 리스트 조회 테스트")
    void getShelterPostsTest() throws Exception {
        // given, when, then
				...
    }
}

@MockBean

spring-boot-test 패키지는 Mockito를 포함하고 있기 때문에 기존에 사용하던 방식대로 Mock 객체를 생성해서 테스트하는 방법도 있지만, spring-boot-test에서는 새로운 방법도 제공한다.

  • 바로 @MockBean 어노테이션을 사용해서 이름 그대로 Mock 객체를 빈으로써 등록할 수 있다.
  • 기존에 사용되던 스프링 Bean이 아닌 Mock Bean을 주입한다.
  • 그렇기 때문에 만일 @MockBean으로 선언된 빈을 주입받는다면 Spring의 ApplicationContext는 Mock 객체를 주입한다.
  • 새롭게 @MockBean을 선언하면 Mock 객체를 빈으로써 등록하지만, 만일 @MockBean으로 선언한 객체와 같은 이름과 타입으로 이미 빈으로 등록되어있다면 해당 빈은 선언한 Mock 빈으로 대체된다.

해당 어노테이션은 테스트 내용 중 외부 서비스를 호출하는 부분을 Mock해서 쉽게 처리할 수 있다.

@SpringBootTest
public class XXXControllerTest {

    @MockBean  // 외부 서비스 호출에 사용되는 RestTemplate Bean을 Mock
    private RestTemplate mockRT;

    @MockBean  // 외부 서비스 호출에 사용되는 Service Bean을 Mock
    private XXXService xXXService;

}

@DataJpaTest

Spring Data JPA를 테스트하고자 한다면 @DataJpaTest 기능을 사용해볼 수 있다.

  • 해당 테스트는 기본적으로 in-memory embedded database를 생성하고 @Entity 클래스를 스캔한다.
  • 일반적인 다른 컴포넌트들은 스캔하지 않는다. 따라서 특정 bean의 의존성이 필요한 경우 아래의 방법 사용
    • @import
    • @DataJpaTest(includeFilters = @Filter(..))

@DataJpaTest는 @Transactional 어노테이션을 포함하고 있다.

  • 따라서 테스트가 완료되면 자동으로 롤백된다.

만약 @Transactional 기능이 필요하지 않다면 아래와 같이 줄 수 있다.

@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class SomejpaTest {
    ...
}

@DataJpaTest 기능을 사용하면 @Entity를 스캔하고 repository를 설정하는 것 이외에도 테스트를 위한 TestEntityManager라는 빈이 생성된다.

  • 이 빈을 사용해서 테스트에 이용한 데이터를 정의할 수 있다.
@DataJpaTest
class SomejpaTest {

    @Autowired
    private TestEntityManager entityManager;

    @Test
    @DisplayName("게시글 아이디로 댓글 목록 삭제 테스트")
    void deleteAllByMissingPostIdTest() {
        // given
        LongStream.rangeClosed(1, 3).forEach(idx ->
            entityManager.persist(Comment.builder()
                .missingPost(missingPost)
                .content("내용")
                .account(account)
                .build()
            )
        );

        // when
        commentRepository.deleteAllByMissingPostId(missingPost.getId());
        List<Comment> comments = commentRepository.findAll();

        // then
        SoftAssertions.assertSoftly(softAssertions -> {
                softAssertions.assertThat(comments).hasSize(3);
                comments.forEach(foundComment -> softAssertions.assertThat(foundComment.isDeleted()).isTrue());
            }
        );
    }

}

만약 테스트에 내장된 임베디드 데이터베이스를 사용하지 않고 real database를 사용하고자 하는 경우, @AutoConfigureTestDatabase 어노테이션을 사용하면 손쉽게 설정할 수 있습니다.

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class SomejpaTest {
    ...
}

사용시 주의할 점

슬라이스 테스트 시, 하위 레이어는 Mock 기반으로 만들기 때문에 주의할 점들이 있다.

  • 의존성 객체를 Mocking하기 때문에 완벽한 테스트는 아님
  • Mocking 처리하기 위한 시간이 소요
  • Mocking 라이브러리에 대한 학습 비용 발생
  • Mock 기반 으로 테스트하기 때문에 실제 환경에서는 결과가 다를 수 있음

참고 출처

profile
동료들이 같이 일하고 싶어하는 백엔드 개발자가 되고자 합니다!

0개의 댓글