-
[알림 서비스] 테스트 코드 작성STOVE DEVCAMP 3기/알림 서비스 2023. 3. 10. 04:20
테스트 코드 작성 규칙을 세워 알림 서비스 구현 시 단위 테스트 코드 작성을 수행함
📌 어느 부분을 작성했는지
- 알림 서비스 구현 시 클린 아키텍처를 도입하여 아래와 같이 구성함 - 참고) 클린 아키텍처 도입
- 따라서 패키지 구조는 다음과 같음
- 헥사고날 아키텍처를 기반으로 비즈니스 로직을 포함한 내부 영역과 외부 영역을 분리하였고, 이를 기반으로 단위 테스트 코드 작성 부분은 아래의 3가지로 설정함
- adapter/in 패키지의 Web Adatper
- adapter/out 패키지의 Persistence Adapter
- application 패키지의 Service
📌 어떤 방식으로 작성했는지
공통
- Given-When-Then 패턴 사용
- 테스트 코드를 작성하는 표현 방식 중 하나임
- Given : 테스트 하기 위해 기본적으로 세팅하는 값
- When : 테스트 하기 위한 조건 지정
- Then : 테스트 하기 위한 행위가 우리가 예상하는대로 동작하는지 검증하는 행동 / 절차
- 성공, 실패 테스트 케이스 작성
adapter/in 패키지의 Web Adatper
- @WebMvcTest 사용
- Application Context를 완전히 시작 시키지 않고, Web Layer에 대한 단위 테스트 수행을 위해 사용함
- HTTP Request의 Param, Request Body에 대한 유효성 검사 수행
작성 예시 - 알림 수신 디바이스 등록 API 호출
@WebMvcTest(DeviceController.class)public class DeviceControllerTest {@AutowiredMockMvc mockMvc;@MockBeanRegisterDeviceUseCase registerDeviceUseCase;@MockBeanDeleteDeviceUseCase deleteDeviceUseCase;@MockBeanUpdateNotificationSettingUseCase updateNotificationSettingUseCase;@MockBeanGetNotificationSettingUseCase getNotificationSettingUseCase;@Test@DisplayName("디바이스 등록 성공")void registerDeviceSuccess() throws Exception {//givenString requestBody = """{"token": "clutter-token"}""";//when, thenmockMvc.perform(post("/api/notification/device").header("userId", "clutter-user-id").content(requestBody).contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()).andExpect(jsonPath("$.code").value("001")).andExpect(jsonPath("$.message").value("성공했습니다.")).andExpect(jsonPath("$.data").isEmpty());}@Test@DisplayName("디바이스 토큰 값이 null인 경우 예외 반환")void throwExceptionIfTokenNull() throws Exception {//givenString requestBody = """{"token": null}""";//when, thenmockMvc.perform(post("/api/notification/device").header("userId", "clutter-user-id").content(requestBody).contentType(MediaType.APPLICATION_JSON)).andExpect(status().is4xxClientError()).andExpect(jsonPath("$.code").value("-401")).andExpect(jsonPath("$.message").value("디바이스 토큰 값은 필수입니다."));}}- @MockBean으로 의존성 주입을 받아 테스트 진행함
adapter/out 패키지의 Persistence Adapter
- @DataMongoTest
- MongoDB의 컴포넌트들을 중점적으로 테스트 가능
- @Document를 스캔하여 MongoTemplate 생성
- 실제 데이터베이스와 통신하여 로직이 예상과 동일하게 동작하는지 검증 수행
작성 예시 - 디바이스 조회 로직
@Disabled@ActiveProfiles("test")@DataMongoTest(excludeAutoConfiguration = MongoDBConfig.class)@Import({DevicePersistenceAdapter.class,DeviceMapper.class,TokenMapper.class,DeviceMongoTemplate.class,MongoDBTestConfig.class})class DevicePersistenceAdapterTest {@AutowiredDevicePersistenceAdapter devicePersistenceAdapter;@AutowiredDeviceRepository deviceRepository;@AutowiredDeviceMongoTemplate deviceMongoTemplate;@AfterEachvoid cleanUp() {deviceRepository.deleteAll();}@Test@DisplayName("디바이스가 존재하는 경우 객체를 반환함")void findByUserIdAndTokenExist() {//givenTokenDocument tokenDocument = TokenDocument.builder().value("clutter-token").timestamp(LocalDateTime.now()).build();DeviceDocument deviceDocument = DeviceDocument.builder().userId("clutter-user").pushNotificationOpt(true).tokenDocument(tokenDocument).build();deviceRepository.save(deviceDocument);//whenDevice device = devicePersistenceAdapter.findByUserIdAndToken("clutter-user", "clutter-token");//thenassertThat(device.getToken().getValue()).isEqualTo("clutter-token");assertThat(device.getUserId()).isEqualTo("clutter-user");}@Test@DisplayName("디바이스가 존재하지 않는 경우 null을 반환함")void findByUserIdAndTokenNotExist() {//whenDevice device = devicePersistenceAdapter.findByUserIdAndToken("clutter-user", "clutter-token");//thenassertThat(device).isNull();}...}application 패키지의 Service
- JUnit
- 내가 작성한 로직이 예상과 동일하게 동작하는지 검증 수행
작성 예시
- 알림 목록 조회 시 사용되는 커서 페이지네이션 로직에 대한 검증 수행함
- 분기문에 대한 테스트 케이스를 상세히 작성함
public class GetNotificationServiceTest {private final GetNotificationPort getNotificationPort = Mockito.mock(GetNotificationPort.class);private final GetNotificationService getNotificationService = new GetNotificationService(getNotificationPort);@Test@DisplayName("알림 데이터 리스트의 크기가 (요청 pageSize + 1)보다 작으면 hasNextPage=false")void hasNextPageFalseIfListSizeLtPageSize() {//givenNotificationListQueryReq listQueryReq = NotificationListQueryReq.builder().userId("clutter-user-id").direction(true).nextIndex(null).pageSize(20).build();List<Notification> notifications = createNotificationList(20);given(getNotificationPort.findAllByUserId(listQueryReq)).willReturn(notifications);//whenNotificationListQueryRes listQueryRes = getNotificationService.getNotificationList(listQueryReq);//thenassertThat(listQueryRes.getNotifications().size()).isEqualTo(20);assertThat(listQueryRes.getNextIndex()).isEqualTo("-1");assertThat(listQueryRes.isHasNextPage()).isFalse();}@Test@DisplayName("알림 데이터 리스트의 크기가 (요청 pageSize + 1) 이상이고 아래 방향 스크롤이면, nextIndex=리스트의 마지막 원소의 id이고 hasNextPage=true")void hasNextPageTrueIfListSizeGtePageSizeWithDownScroll() {//givenNotificationListQueryReq listQueryReq = NotificationListQueryReq.builder().userId("clutter-user-id").direction(true).nextIndex(null).pageSize(20).build();List<Notification> notifications = createNotificationList(21);given(getNotificationPort.findAllByUserId(listQueryReq)).willReturn(notifications);//whenNotificationListQueryRes listQueryRes = getNotificationService.getNotificationList(listQueryReq);//thenassertThat(listQueryRes.getNotifications().size()).isEqualTo(20);assertThat(listQueryRes.getNextIndex()).isEqualTo("notification-id1");assertThat(listQueryRes.isHasNextPage()).isTrue();}@Test@DisplayName("알림 데이터 리스트의 크기가 (요청 pageSize + 1) 이상이고 위 방향 스크롤이면, nextIndex=리스트의 첫번째 원소의 id이고 hasNextPage=true")void hasNextPageTrueIfListSizeGtePageSizeWithUpScroll() {//givenNotificationListQueryReq listQueryReq = NotificationListQueryReq.builder().userId("clutter-user-id").direction(false).nextIndex("notification-id1").pageSize(20).build();List<Notification> notifications = createNotificationList(21);given(getNotificationPort.findAllByUserId(listQueryReq)).willReturn(notifications);//whenNotificationListQueryRes listQueryRes = getNotificationService.getNotificationList(listQueryReq);//thenassertThat(listQueryRes.getNotifications().size()).isEqualTo(20);assertThat(listQueryRes.getNextIndex()).isEqualTo("notification-id21");assertThat(listQueryRes.isHasNextPage()).isTrue();}...}'STOVE DEVCAMP 3기 > 알림 서비스' 카테고리의 다른 글
[알림 서비스] 클린 아키텍처 (0) 2023.03.10 [개념] Kafka 컨슈머 (0) 2023.03.10 [개념] Kafka 리밸런싱 (0) 2023.03.10 [알림 서비스] 알림 서비스 구조 개선 방향 수립 (0) 2023.03.10 [알림 서비스] 알림 서비스 구조 개선 - 대량의 알림 데이터 처리를 위한 장기적 관점의 확장 가능한 구조 설계 (0) 2023.03.10