-
무시무시한 소스코드 중복을 줄이는 메서드화의 힘 (레벨 테스트 6일차 작업 회고)Today I Learned 2022. 10. 10. 23:53
백엔드에서 회원가입 예외처리 로직을 짜는 데 오후 일과 시간을 할애했다. @RequestBody로 전달되는 DTO 데이터들의 상태를 검사해 예외를 처리해줄 수 있을 경우 일차적으로 예외를 발생시키도록 Spring Validation 관련 어노테이션들을 사용했다.
DTO에서 @NotBlank와 @Pattern 어노테이션으로 각 데이터가 지정한 유효성에 맞는지 검사하고, 유효하지 않은 결과가 Controller에서 BindingResult의 구현체를 통해 전달되어 오류가 있음이 확인될 경우 오류 메세지를 받아 Exception을 발생시키도록 했다.
테스트에서는 예외에 해당되는 상황이 발생했을 때, 예외를 정상적으로 발생시키는지 테스트를 수행해줘야 했다. 일단은 하나의 테스트 메서드마다 MockMvc를 이용해 모의로 요청을 전송하고 그에 대한 응답을 예측하는 테스트를 작성했다.
@Test void nameWithBlank() { mockMvc.perform(MockMvcRequestBuilders.post("/user") .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .content("{" + "\"name\":\"\"," + "\"identification\":\"hsjkdss228\"," + "\"password\":\"Megaptera!1\"," + "\"confirmPassword\":\"Megaptera!1\"" + "}")) .andExpect(MockMvcResultMatchers.status().isBadRequest()) .andExpect(MockMvcResultMatchers.content().string( containsString("Blank name") )); }
문제는 이런 식의 개별 테스트를 모든 예외상황에 대해 작성하니, 테스트 코드의 길이가 400줄을 거뜬히 넘어가기 시작했다. 어떤 테스트에서 어떤 예외처리를 검사하고 있는지 스크롤하면서 찾기 힘든 수준의 양이 되자 이대로 계속 테스트를 추가하면 곤란하겠다는 생각이 들었다.
테스트들을 자세히 들여다보니 예외처리되는 테스트들은 모두 같은 로직을 따르고 있었다. 똑같은 경로로 POST 요청이 이루어지고, 응답은 모두 Bad Request이고, JSON으로 주는 데이터도 예외적으로 다른 값을 부여해야 하는 부분이나 그로 인해 달라지는 출력될 에러 코드를 제외하면 모두 공통적인 입력값으로 대체할 수 있을 것 같았다.
바로 메서드화를 시도했다.
void mockMvcPerformAndExpectWhenBadRequest( String name, String identification, String password, String confirmPassword, String expectedString) throws Exception { mockMvc.perform(MockMvcRequestBuilders.post("/user") .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .content("{" + "\"name\":\"" + name + "\"," + "\"identification\":\"" + identification + "\"," + "\"password\":\"" + password + "\"," + "\"confirmPassword\":\"" + confirmPassword + "\"" + "}")) .andExpect(MockMvcResultMatchers.status().isBadRequest()) .andExpect(MockMvcResultMatchers.content().string( containsString(expectedString) )); }
정의한 하나의 메서드를 아래와 같은 식으로 인자만 다르게 주어서 각각의 테스트에서 사용하도록 했다.
@Test void registerWithBlankIdentification() throws Exception { mockMvcPerformAndExpectWhenBadRequest( name, "", password, password, "1001"); } @Test void registerWithBlankPassword() throws Exception { mockMvcPerformAndExpectWhenBadRequest( name, identification, "", password, "1002"); } @Test void registerWithIdentificationMoreThan16Word() throws Exception { String wrongIdentification = "kingchikorita1234"; mockMvcPerformAndExpectWhenBadRequest( name, wrongIdentification, password, password, "1005"); } @Test void registerWithIdentificationWithoutLowerCase() throws Exception { String wrongIdentification = "12412412"; mockMvcPerformAndExpectWhenBadRequest( name, wrongIdentification, password, password, "1005"); } // ...
테스트를 수행한 결과 메서드로 분리하기 전과 같은 결과를 도출함을 확인할 수 있었다.
소스코드를 작성하다 보면 비슷한 방식으로 수행되는 테스트가 많아지면서 중복이 많이 발생하게 되는 경향이 있는데, 테스트 코드에서도 중복을 제거할 수 있다면 많은 경우의 수를 검사해야 하는 경우에도 겁내지 않고 테스트를 시도해볼 수 있을 것 같다.
P.S. 2022년 10월 11일 내용 추가
UserController에 추가적인 REST API를 정의하던 중, 성공 케이스를 검증하는 Post 요청에 대한 기대 응답을 Created가 아닌 Ok로 두고 테스트를 수행하고 있던 것을 발견했다. 즉각적으로 소스코드에서 @ResponseStatus 어노테이션을 통해 응답 상태를 Created를 설정해준 뒤, 테스트 코드를 수정했다.
테스트 코드의 수정은 매우 간단했다. 메서드로 추출한 mockMvcPerformAndExpectWhenOk()의 이름을 Ok에서 Created로 변경해주고, 메서드 내에서 응답을 검증하는 .andExpect(MockMvcResultMatchers.status().isOk())에서 Ok를 Created로 변경해주는 단 두 번의 과정으로 끝났다.
생각해보니 성공하는 경우에 대한 테스트는 단 하나뿐이긴 했지만, 그럼에도 불구하고 '혹시 빼먹고 수정하지 않은 테스트 코드가 없을까?' 에 대해 생각하는 인지 과정을 줄일 수 있다는 것만으로도 중복 제거의 힘을 다시 한 번 확인한 순간이 아닌가 싶었다.
'Today I Learned' 카테고리의 다른 글
jest.fn()을 부여한 변수에는 호출 기록이 쌓인다 (0) 2022.10.12 강행돌파는 결국 언젠가 한 번은 해야 한다 (0) 2022.10.11 성능의 최적화가 오히려 코드 가독성에 좋지 않은 영향을 미칠 수 있다 (0) 2022.10.09 @SpyBean, @MockBean (0) 2022.10.08 관심사의 분리를 잘 하면 테스트 코드 작성이 수월해진다 (레벨 테스트 5일차 작업 회고) (0) 2022.10.07