Plog의 Service Test에 도달하기 위한 한 발자국

Plog의 Service Test에 도달하기 위한 한 발자국

테스트를 하게 된 이유

테스트는 소프트웨어의 기능과 동작을 확인해 결함을 찾아내고, 원하는 결과가 나오는지 확인할 수 있는 방법입니다.

프로젝트 초반에 전체적인 틀을 잡는데 시간이 오래걸렸고, 큰 틀이 여러 번 수정되는 일이 있었습니다.

그런데 그럴 때마다 새로운 부분에 대해서는 잘 작성하였지만 기존 코드는 정상작동하는지 확인이 다 되지 않은 채로 배포하는 일이 잦아졌고 나중에서야 결함이 생기게 되었다는 것을 확인했습니다.

완성된 부분에 대해서는 미리 Test Code를 작성한다면, 소프트웨어를 수정하면서 예상하지 못했던 결함을 방지해나갈 수 있다고 생각이 되어 Test를 하게 되었습니다.

어떻게 테스트를 할 것인가?

저희는 Unit testingJUnit5를 사용하였습니다.

JUnit5

JUnit은 Spring과 통합할 수 있다는 장점때문에 Spring을 사용하는 프로젝트에서 많이 사용되는 단위 테스트 프레임워크입니다.

특히 JUnit5는 Spring Boot 2.2버전 이상부터 기본으로 제공되기 때문에 쉽게 도입하여 사용할 수 있었습니다.

또한 JUnit5JUnit Platform + JUnit Vinatge + JUnit Jupiter를 합친 것으로, 여기서 JUnit Platform은 JVM에서 별도의 엔진으로 테스트를 실행할 수 있다는 장점도 있었습니다.

junit5

저희는 JUnit5를 사용하여 Service의 각 메서드의 테스트를 시행하였으며 Mockito를 사용하여 Mock객체를 만들어 테스트를 시행하였습니다.

Test 구현

각 메서드에서 발생할 수 있는 모든 에러와 정상동작을 정의하여 테스트를 작성하였습니다.

가령 아래와 같이 블로그를 구독하는 메서드가 있습니다.

/blog/service/HomeService.java
public void createSubscribe(Long loginedUserID, @NotNull CreateSubscribeRequest request) throws BlogNotFoundException {
    Blog blog = blogRepository.findById(request.getBlogID()).orElseThrow(BlogNotFoundException::new);
    User user = userRepository.findById(request.getUserID()).orElseThrow(UserNotFoundException::new);
    if (!user.getId().equals(loginedUserID)) {
        throw new NotProperAuthorityException();
    }
    if (blog.isSelfSubscribe(user)) {
        throw new SelfSubscribeException();
    }
    if (subscribeRepository.existsByUserIDAndBlogId(user.getId(), blog.getId())) {
        throw new SubscribeDuplicatedException();
    }
    subscribeRepository.save(request.toEntity(blog));
}

서비스 로직 상 발생할 수 있는 에러의 종류는 아래와 같습니다.

  • 구독하려는 블로그 ID가 없을 경우
  • 구독을 요청하는 유저의 ID가 없을 경우
  • 구독을 요청하는 유저의 ID가 로그인한 유저의 ID와 같지 않을 경우
  • 자신의 블로그를 구독하려고 하는 경우
  • 이미 구독한 블로그를 구독하려고 하는 경우

따라서 이 5가지의 에러에 정상동작을 포함하여 이 메서드는 다음과 같이 6개의 테스트코드를 작성하였습니다.

/test/blog/service/HomeServiceTest.java
@Test
@DisplayName("createSubscribe - 블로그가 없는 경우 테스트")
void testCreateSubscribe_1() {}

@Test
@DisplayName("createSubscribe - 유저가 없는 경우 테스트")
void testCreateSubscribe_2() {}

@Test
@DisplayName("createSubscribe - 로그인한 유저와 요청 유저가 다른 경우 테스트")
void testCreateSubscribe_3() {}

@Test
@DisplayName("createSubscribe - 본인을 구독하는 경우 테스트")
void testCreateSubscribe_4() {}

@Test
@DisplayName("createSubscribe - 중복으로 블로그를 구독하는 경우 테스트")
void testCreateSubscribe_5() {}

@Test
@DisplayName("createSubscribe - 정상동작")
void testCreateSubscribe_6() {}

하나의 메서드에 대한 테스트 코드는 뒤에 넘버링을 추가하였으며, DisplayName에는 테스트하고자 하는 내용을 명시적으로 작성하였습니다.

또한 세부 구현은 BDD(Behavior Driven Development)을 따랐으며, Given-When-Then Pattern에 따라 아래와 같이 각 파트를 나누어 작성하였습니다.

/test/blog/service/HomeServiceTest.java
@Test
@DisplayName("createSubscribe - 정상동작")
void testCreateSubscribe_6() {
    // given
    when(
        blogRepository.findById(1L)
    ).thenReturn(
        Optional.of(Blog.builder().id(1L).user(User.builder().id(2L).build()).build())
    );
    when(
        userRepository.findById(1L)
    ).thenReturn(
        Optional.of(User.builder().id(1L).build())
    );

    // when
    homeService.createSubscribe(1L, CreateSubscribeRequest.builder().userID(1L).blogID(1L).build());

    // then
    verify(subscribeRepository, times(1)).save(any());
}

이를 실행해보면 다음과 같은 결과를 확인할 수 있습니다.

service_test_result