본문 바로가기
게시판 만들기(Spring Boot, JPA)

게시판 만들기 04 - 테스트 코드 작성(PostService)

by burpee 2022. 12. 8.

이번 포스팅은 게시글 CRUD API의 테스트 코드를 작성해 보려고 한다. 나는 PostController, PostService 두 가지를 나눠서 각각 6개씩 테스트 코드를 작성했다.

  1. 게시글 쓰기
  2. 게시글 단건 조회
  3. 게시글 조회 실패 - 잘못된 ID
  4. 게시글 페이징 조회
  5. 게시글 수정
  6. 게시글 삭제

나는 Controller와 Service 두 개의 계층에서 테스트 코드를 작성했으니 12개의 테스트 코드를 작성했지만, 포스팅에서는 PostService에서 1, 2, 3번의 테스트 코드를 보여주고 PostController에서 4, 5, 6번의 테스트 코드를 보여주려고 한다.

PostService 테스트 코드 작성

테스트 코드는 [ic]src/test/[/ic] 패키지 안에 작성을 한다. 테스트 코드 파일을 직접 만들어도 되고, 단축키를 이용해서 만들 수도 있다. intelliJ에서는 보통 테스트 코드를 작성하고 싶은 곳에서 [ic] command + shift + T [/ic]를 누르면 된다.

 

PostServiceTest

@SpringBootTest
class PostServiceTest {

    @Autowired
    private PostService postService;

    @Autowired
    private PostRepository postRepository;

    @BeforeEach
    void clean() {
        postRepository.deleteAll();
    }
}

 

나도 같은 방식으로 PostService에서 단축키를 눌러 테스트 코드를 작성할 PostServiceTest 클래스를 생성했다. 테스트 코드는 src/test/ 패키지 밑에 생성되고 src/main 밑에 있는 패키지 구조와 동일한 패키지 밑에 자동으로 생성된다.

테스트 코드를 처음 생성하면 위의 코드와 같지 않고 비어 있을 것이다. 나는 [ic]@SpringBootTest[/ic] 어노테이션을 붙여서 스프링 컨테이너를 띄워서 테스트를 진행하려고 한다. 그리고 테스트에 필요한 PostService, PostRepository에 의존성 주입을 해준다. 마지막으로 [ic]@BeforeEach [/ic] 어노테이션을 사용해서 각 테스트 메서드 실행 전에 테스트 데이터를 지워주는 clean() 메서드를 작성한다. 이 메서드는 CRUD 테스트를 하면서 쌓인 게시글 테스트 데이터를 비워주기 때문에 각 테스트를 안정성 있게 진행하게 해 준다.

 

테스트 메서드 작성 

게시글 쓰기

첫 번째로 게시글 쓰기 테스트 코드를 작성해 보았다. 테스트 메서드는 [ic]@Test [/ic] 어노테이션을 붙여서 작성한다. [ic]@DisplayName[/ic] 은 테스트에는 영향이 없고 어떤 테스트인지 보기 쉽게 하려고 사용했다. 그리고 테스트 코드를 작성할 때 given, when, then으로 나누어서 준비 - 실행 - 검증 로직을 보기 쉽게 하였다. 테스트 코드를 작성할 때 가장 많이 사용하는 패턴인 것 같다.

 

@Test
@DisplayName("게시글 쓰기")
void writeTest() {
    // given
    RequestRegisterPostDto requestDto = RequestRegisterPostDto.builder()
            .title("test title")
            .content("test content")
            .build();

    // when
    ResponseSavedIdDto responseSavedIdDto = postService.write(requestDto);

    // then
    Post post = postRepository.findById(responseSavedIdDto.getSavedId())
            .orElseThrow(() -> new EntityNotFoundException("게시글이 존재하지 않습니다."));

    assertEquals(responseSavedIdDto.getSavedId(), post.getPostId());
    assertEquals("test title", post.getTitle());
    assertEquals("test content", post.getContent());
}

 

작성한 코드를 살펴보면 가장 먼저 게시글 저장 요청을 할 RequestRegisterPostDto를 만들어주고 [ic] postService.write(requestDto)[/ic]를 통해서 게시글을 저장한다. 그리고 저장한 게시글의 ID 값을 받아온 후에 해당 ID를 사용해서 저장한 Post 엔티티를 찾아온다. 마지막으로 저장한 게시글 ID, 제목, 내용이 RequestRegisterPostDto를 만들 때와 같은 값인지 검증한다.

검증하기 전까지의 코드는 테스트 코드를 작성해보지 않았어도 이해할 수 있는 코드지만 검증 부분에서 [ic] assertEquals()[/ic]라는 처음 보는 키워드가 등장했다. Junit을 사용하여 테스트를 할 때 Assertions라는 것을 사용하는데 Assertions에는 데이터를 검증할 수 있는 여러 가지 메서드들이 있다.

 

  • assertEquals(a, b)
  • assertSame(a, b)
  • assertNull(a)
  • assertNotNull(a)
  • assertTrue(a)
  • assertFalse(a)
  • assertThrows(a, b)

 

위와 같이 다양한 assert 메서드를 활용해서 테스트 실행 결과를 검증할 수 있다. 첫 테스트 코드이기 때문에 여러 가지 간단한 설명과 함께 게시글 쓰기 테스트를 끝내도록 하겠다.

테스트 코드를 실행한 결과는 아래 사진과 같다. 초록색 체크 표시가 들어온다면 테스트에 성공한 것이다.

 

게시글 쓰기 테스트 결과 - 성공

 

만약 저장된 데이터 값과 다른 데이터로 테스트 코드를 실행하면 당연히 테스트에 실패할 것이다. 테스트를 실패한 모습은 초록색 체크가 아니라 아래와 같은 화면이 보일 것이다.

 

게시글 쓰기 테스트 결과 - 실패

또한 친절하게 어떤 데이터가 잘못 돼서 테스트 통과를 못했는지 알려준다. 테스트에 실패하더라도 이런 로그들을 보고 천천히 테스트 코드를 수정해 나가면 된다.

 

게시글 쓰기 테스트 결과 - 실패 이유

 

게시글 단건 조회

@Test
@DisplayName("게시글 단건 조회")
void getTest() {
    // given
    ResponseSavedIdDto responseSavedIdDto = postService.write(RequestRegisterPostDto.builder()
            .title("test title")
            .content("test content")
            .build());

    Long postId = responseSavedIdDto.getSavedId();

    // when
    ResponsePostDto responsePostDto = postService.get(postId);

    // then
    assertEquals(postId, responsePostDto.getPostId());
    assertEquals("test title", responsePostDto.getTitle());
    assertEquals("test content", responsePostDto.getContent());
}

 

게시글 단건 조회 테스트이다. 이 전에 작성했던 게시글 쓰기 테스트와 조금 비슷해 보인다. 조금 다른 점은 게시글 쓰기 테스트는 PostService 내부에 [ic] write()[/ic] 메서드를 테스트하는 것이고, 게시글 조회 테스트는 PostService의 [ic]get(postId)[/ic] 메서드를 테스트하는 것이다. 

어쨌든 테스트 데이터를 저장하고 게시글 조회 메서드인 [ic] get(postId)[/ic] 메서드로 가져온 데이터를 [ic] assertEquals()[/ic] 메서드를 사용해서 검증을 한다.

 

게시글 단건 조회 테스트 결과 - 성공

 

게시글 조회 실패 - 잘못된 ID

@Test
@DisplayName("게시글 조회 실패 - 잘못된 ID")
void getFailBadIdTest() {
    // given
    ResponseSavedIdDto responseSavedIdDto = postService.write(RequestRegisterPostDto.builder()
            .title("test title")
            .content("test content")
            .build());

    Long postId = responseSavedIdDto.getSavedId();

    // when, then
    RuntimeException runtimeException = assertThrows(RuntimeException.class,
            () -> postService.get(postId + 1L)
    );
    assertEquals("존재하지 않는 게시글입니다.", runtimeException.getMessage());
}

 

게시글 조회 실패 테스트이다. 코드 흐름을 보면 게시글을 저장할 Dto를 만들어서 저장을 하고, 반환받은 게시글 ID에 1을 더해서 일부러 해당 ID와 다른 ID로 게시글을 조회한다. 그럼 PostService의 [ic] get(postId)[/ic] 메서드는 RumtimeException을 throw 하게 된다. 테스트 코드에서는 잘못된 ID로 게시글을 찾을 때 RumtimeException을 뱉는 것을 예상하고 있기 때문에 throw 결과가 RumtimeException인지 [ic] assertThrows()[/ic]를 사용해서 검증한다.

 

지금까지 사용한 [ic] assertEquals(a, b)[/ic] 메서드는 a와 b가 같은 값인지 검증했다. 하지만 [ic]assertThrows(a, b)[/ic] 메소드는 b 로직이 발생했을 때, a 예외(Exception)가 터지는지 검증한다.

 

게시글 조회 실패 테스트 결과 - 성공

 

처음에 게시글 조회를 실패하는 로직인데 테스트를 통과해서 초록불이 뜨는 게 좀 의아했던 기억이 있다. 왜냐하면 실패 로직인데 성공이 떠서 부조화가 오는 느낌? 이 들었기 때문이다. 하지만 우리는 RuntimeException이 터질 거라고 생각하고 정확히 테스트 코드를 짰기 때문에 초록불이 들어온 것이다. 만약 검증을 다른 Exception으로 했다면 테스트에 실패했을 것이다.

 

아래 결과는 EntityNotFoundException으로 검증한 결과다. 우리가 기대한 Exception으로 테스트하지 않았기 때문에 당연히 실패한다.

 

게시글 조회 실패 테스트 결과 - 실패

 

org.opentest4j.AssertionFailedError:
Unexpected exception type thrown ==>
expected: <javax.persistence.EntityNotFoundException> 
but was: <java.lang.RuntimeException>

 

로그를 보면 실제 발생한 예외는 RuntimeException이고 우리가 검증하려고 넣은 예외는 EntityNotFoundException이기 때문이 실패했다고 나온다.

 

마지막으로 PostServiceTest의 테스트 코드들을 전부 실행하고 성공한 결과를 보여주면서 포스팅을 마치려고 한다.

 

PostServiceTest 전체 테스트 결과

 

다음 포스팅은 PostController의 테스트 코드를 작성해 봐야겠다.


소스 코드

https://github.com/cheoljin408/simple-board-api

댓글