본문 바로가기
Side Project/Socket

[Test]단위테스트 시 List가 empty & null일 경우 테스트 작성 팁

by asdft 2024. 2. 4.
  @Test
  @WithMockUser(username = "1", authorities = "ROLE_USER")
  void skillNames_size가_0이고_요청이_유효하면_201_응답을_한다() throws Exception {
    PostSaveRequestDto requestBody = new PostSaveRequestDto("title", "content", PostType.PROJECT,
        PostMeeting.ONLINE, Collections.emptyList());

    when(postSaveUseCase.createPost(any())).thenReturn(Post.builder().id(1L).build());

    mockMvc.perform(post("/posts")
            .header("Authorization", "Bearer access-token")
            .contentType(APPLICATION_JSON)
            .content(objectMapper.writeValueAsBytes(requestBody)))
        .andExpectAll(
            status().isCreated(),
            header().string("Location", containsString("posts/1"))
        );
  }

  @Test
  @WithMockUser(username = "1", authorities = "ROLE_USER")
  void skillNames가_null이고_요청이_유효하면_201_응답을_한다() throws Exception {
    PostSaveRequestDto requestBody = new PostSaveRequestDto("title", "content", PostType.PROJECT,
        PostMeeting.ONLINE, null);

    when(postSaveUseCase.createPost(any())).thenReturn(Post.builder().id(1L).build());

    mockMvc.perform(post("/posts")
            .header("Authorization", "Bearer access-token")
            .contentType(APPLICATION_JSON)
            .content(objectMapper.writeValueAsBytes(requestBody)))
        .andExpectAll(
            status().isCreated(),
            header().string("Location", containsString("posts/1"))
        );
  }

 

위 코드는 List<String> 인 skillNames인 해시태그 입력 란에, List가 null 이거나 empty 일 경우의 테스트 성공여부를 알아보기 위해 작성한 테스트 코드이다. 물론 위 방법도 돌아가는데 문제는 없지만 테스트코드가 길어지고 지저분해 보였다. 

 

그래서 String 타입 테스트시 사용했던 @NullAndEmptySource가 String 타입인 List에도 쓸 수 있나 해서 코드를 타고 들어가봤다.

 

 

@EmptySource에서 List타입의 파라미터의 경우, Collections.emptyList()를 Stream객체로 반환하는 것을 확인할 수 있다.

 

하지만 여기서 한가지 고민이 생겼다.

비록 테스트가 성공하는것을 확인 했지만,

List<String> 타입인 skillNames에, Collections.emptyList( )가 아닌 Stream 객체로 감싼 Stream.of(arguments(Collections.emptyList( ))) 반환한 값을 넣어도 아무 문제가 없을까 였다.

Stream과 Collection 모두 연속된 요소 형식의 값을 저장하는 자료구조의 인터페이스를 제공한다. 
연속된이란, 순서와 상관없이 아무 값에나 접속하는 것이 아닌 순차적으로 접근한다는 것을 의미한다.
스트림과 컬렉션의 차이는 데이터를 언제 계산하느냐이다.

Steam과 Collection의 가장 큰 차이는, 데이터 계산을 하는 시점이다.
Collection의 경우 현재 자료구조(LinkedList, ArrayList)가 포함하는 모든 데이터를 메모리에 저장하는 자료구조이다. 즉, Collection에 포함될 각 요소들은 포함이 되기전에 꼭 계산이 완료되어야한다.

반면에 Stream의 경우에는 요청할 때만 요소를 계산하는 고정된 자료구조(Stream에 요소를 추가 또는 삭제 불가)이다.

 

위에서 볼 수 있듯이 Collection과 Stream의 가장 큰 차이점은 연산 시점이다.

따라서 자료를 빼고 넣는 연산 없이 단순히 연속된 요소 형식의 값을 저장하는 자료구조로써 사용할때는 고민할 필요가 없었던 것이다.

 

그래서 위의 긴 테스트 코드를 아래와 같이 바꿔줬다.

  @ParameterizedTest(name = "skillNames가_{0}이면_201_응답을_한다")
  @NullAndEmptySource
  @WithMockUser(username = "1", authorities = "ROLE_USER")
  void PostSaveRequestDto_skillNames_is_valid_with_Null_and_Empty(List<String> skillNames)
      throws Exception {
    PostSaveRequestDto requestBody = new PostSaveRequestDto("title", "content", PostType.PROJECT,
        PostMeeting.ONLINE, skillNames);

    when(postSaveUseCase.createPost(any())).thenReturn(Post.builder().id(1L).build());

    mockMvc.perform(post("/posts")
            .header("Authorization", "Bearer access-token")
            .contentType(APPLICATION_JSON)
            .content(objectMapper.writeValueAsBytes(requestBody)))
        .andExpectAll(
            status().isCreated(),
            header().string("Location", containsString("posts/1"))
        );
  }

 

@NullAndEmptySource의 어노테이션의 경우 String 타입의 파라미터를 테스트할때만 사용했는데, 앞으로도 List타입의 파라미터를 테스트 할 경우 자주 사용해야 겠다.

 

 

 

출처

https://galid1.tistory.com/674 [배움이 즐거운 개발자:티스토리]