테스트
- Mockito + JUnit 방식 (위 예제와 같은 방식)
- 장점:
- 개별 의존성을 모의(mock)하여 단위 테스트에 집중할 수 있음
- 테스트가 빠르고, 간단한 단위 테스트 작성에 적합함
- 단점:
- 많은 모의 객체와 반복되는 설정 코드가 필요할 수 있음
- 복잡한 객체 생성 로직이 중복되면 셋업 코드가 장황해질 수 있음
- 장점:
- BDD 스타일 (Given-When-Then)로 작성
- 장점:
- 테스트의 흐름이 자연어에 가깝게 표현되어 가독성이 높음
- Mockito의 BDDMockito를 사용하면 테스트의 의도를 명확하게 전달할 수 있음
- 단점:
- 팀 내에 BDD 패턴에 익숙하지 않다면 초기 학습 비용이 있음
- 기존 코드와의 일관성 문제가 발생할 수 있음
- 장점:
- Spring Boot Test 활용 (예: @SpringBootTest, @DataJpaTest)
- 장점:
- 실제 스프링 컨텍스트를 로드하여 통합 테스트와 유사하게 테스트할 수 있음
- 레포지토리나 서비스 간의 실제 연동을 검증할 수 있음
- 단점:
- 테스트 실행 속도가 느려질 수 있음
- 단위 테스트보다는 통합 테스트에 가깝게 동작하므로, 범위가 넓어질 수 있음
- 장점:
- Spock 또는 AssertJ 같은 대체 테스트 프레임워크 사용
- 장점:
- Spock은 Groovy 기반으로 보다 직관적이고 간결한 테스트 코드를 작성할 수 있음
- AssertJ를 사용하면 가독성이 높은 어설션 구문을 사용할 수 있음
- 단점:
- 기존 Java 기반 프로젝트에 도입 시 추가 학습이 필요할 수 있음
- 환경 설정과 빌드 설정이 추가될 수 있음
- 장점:
package com.welcommu.moduleservice.projectProgess;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.welcommu.moduledomain.project.Project;
import com.welcommu.moduledomain.projectprogress.ProjectProgress;
import com.welcommu.modulerepository.project.ProjectRepository;
import com.welcommu.modulerepository.projectprogress.ProjectProgressRepository;
import com.welcommu.moduleservice.projectProgess.dto.ProgressCreateRequest;
import java.time.LocalDateTime;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class ProjectProgressServiceTest {
// Mockicto 라이브러리를 이용한 Mock 객체 생성
@Mock
private ProjectRepository projectRepository;
@Mock
private ProjectProgressRepository progressRepository;
@InjectMocks
private ProjectProgressService projectProgressService;
// 도메인 엔티티는 그 자체로 단순한 데이터 객체이며,
// 내부에 복잡한 로직이나 외부 의존성이 없기 때문에 굳이 목(mock) 객체로 생성할 필요가 없음
private Project project;
private ProjectProgress projectProgress;
@BeforeEach
public void setUp() {
// 테스트용 프로젝트 생성
project = new Project();
project.setId(1L);
project.setName("Test Project");
project.setCreatedAt(LocalDateTime.now());
// 테스트용 프로젝트 진행 상태 생성
projectProgress = new ProjectProgress();
projectProgress.setId(1L);
projectProgress.setName("Initial Progress");
projectProgress.setProject(project);
}
@Test
public void testCreateProgress() {
Long projectId = 1L;
log.info("\n테스트 : CreateProgress with projectId: {}", projectId);
// ProgressCreateRequest 를 모의(Mock) 객체로 생성 (by Mockito Library)
ProgressCreateRequest request = mock(ProgressCreateRequest.class);
ProjectProgress progressRequest = new ProjectProgress();
progressRequest.setName("생성된 단계");
// toEntity 메서드에 대해 stub 처리
when(request.toEntity(project)).thenReturn(progressRequest);
when(projectRepository.findById(projectId)).thenReturn(Optional.of(project));
when(progressRepository.findMaxPositionByProjectId(projectId)).thenReturn(Optional.of(6.0f));
projectProgressService.createProgress(projectId, request);
log.info("\n테스트 : After createProgress: progress position is {}", progressRequest.getPosition());
// 생성된 progress 의 position 값이 최대값(10.0f)으로 설정되었는지 확인
assertEquals("생성된 단계", progressRequest.getName());
assertEquals(6.0f, progressRequest.getPosition());
verify(progressRepository).save(progressRequest);
log.info("\n테스트 : Completed testCreateProgress successfully.");
}
@Test
public void testUpdateProgress_Success() {
Long projectId = 1L;
Long progressId = 1L;
ProgressUpdateRequest request = new ProgressUpdateRequest();
// 초기값으로 다른 이름을 설정해서 변경되는 것을 확인할 수 있도록 합니다.
request.setName("매칭되는 상황에서 수정된 단계");
request.setPosition(4.5f);
when(projectRepository.findById(projectId)).thenReturn(Optional.of(project));
when(progressRepository.findById(progressId)).thenReturn(Optional.of(projectProgress));
log.info("Before update: projectProgress name = {}", projectProgress.getName());
log.info("Before update: projectProgress position = {}", projectProgress.getPosition());
projectProgressService.updateProgress(projectId, progressId, request);
log.info("After update: projectProgress name = {}", projectProgress.getName());
log.info("After update: projectProgress position = {}", projectProgress.getPosition());
assertEquals("매칭되는 상황에서 수정된 단계", projectProgress.getName());
verify(progressRepository).save(projectProgress);
}
@Test
public void testUpdateProgress_Mismatch() {
Long projectId = 1L;
Long progressId = 1L;
ProgressCreateRequest request = new ProgressCreateRequest();
projectProgress.setName(request.getName());
Project differentProject = new Project();
differentProject.setId(2L);
differentProject.setName("매칭되지 않는 경우에 수정된 단계");
differentProject.setCreatedAt(project.getCreatedAt());
projectProgress.setProject(differentProject);
when(projectRepository.findById(projectId)).thenReturn(Optional.of(project));
when(progressRepository.findById(progressId)).thenReturn(Optional.of(projectProgress));
CustomException exception = assertThrows(CustomException.class, () -> {
projectProgressService.updateProgress(projectId, progressId, request);
});
assertEquals(CustomErrorCode.MISMATCH_PROJECT_PROGRESS, exception.getErrorCode());
}
@Test
public void testDeleteProgress_Success() {
Long projectId = 1L;
Long progressId = 1L;
projectProgress.setProject(project);
when(projectRepository.findById(projectId)).thenReturn(Optional.of(project));
when(progressRepository.findById(progressId)).thenReturn(Optional.of(projectProgress));
projectProgressService.deleteProgress(projectId, progressId);
verify(progressRepository).delete(projectProgress);
}
@Test
public void testDeleteProgress_Mismatch() {
Long projectId = 1L;
Long progressId = 1L;
// 불일치 상황을 위해 다른 프로젝트 정보 설정
Project differentProject = new Project();
differentProject.setId(2L);
differentProject.setName("매칭되지 않는 경우에 삭제된 단계");
differentProject.setCreatedAt(project.getCreatedAt());
projectProgress.setProject(differentProject);
when(projectRepository.findById(projectId)).thenReturn(Optional.of(project));
when(progressRepository.findById(progressId)).thenReturn(Optional.of(projectProgress));
CustomException exception = assertThrows(CustomException.class, () -> {
projectProgressService.deleteProgress(projectId, progressId);
});
assertEquals(CustomErrorCode.MISMATCH_PROJECT_PROGRESS, exception.getErrorCode());
}
@Test
public void testGetProgressList() {
Long projectId = 1L;
List<ProjectProgress> progressList = Collections.singletonList(projectProgress);
when(projectRepository.findById(projectId)).thenReturn(Optional.of(project));
when(progressRepository.findByProject(project)).thenReturn(progressList);
ProgressListResponse response = projectProgressService.getProgressList(projectId);
assertNotNull(response);
}
}
- @SpringBootTest를 사용하지 않는 경우(JUnit 5의 @ExtendWith + Mockito) : Spring Boot 없이 JUnit 5에서 Mockito를 사용할 때는 @ExtendWith(MockitoExtension.class)를 사용하여 Mockito 확장을 활성화할 수 있습니다. 이 방법은 @RunWith(MockitoJUnitRunner.class)와 비슷하지만 JUnit 5에 맞게 설정됩니다.
- 그렇다면 확장을 사용하지 않으면 어떨까요?
- 테스트 실행 전후에 추가적인 동작(예: 리소스 초기화, 외부 시스템과의 연결 등)을 제어할 방법이 제한됩니다. 하지만 @ExtendWith를 사용하면 beforeEach, afterEach 메서드를 통해 테스트 메서드 전후의 작업을 제어할 수 있습니다.
- 확장을 사용하면 뭐가 좋을까요?
- @ExtendWith를 사용하여 확장 기능을 추가하면, 테스트 메서드의 실행을 조건부로 제어할 수 있습니다. 예를 들어, 특정 조건에 따라 테스트를 실행하거나, 특정 환경에서만 테스트가 실행되도록 설정할 수 있습니다.
- 외부 라이브러리나 프레임워크의 확장 기능을 손쉽게 통합할 수 있습니다. 예를 들어, JUnit 5에서 제공하는 확장 외에도, Mockito나 Spring Test와 같은 외부 라이브러리의 기능을 통합할 때 유용합니다.
- 그렇다면 확장을 사용하지 않으면 어떨까요?
728x90
'About Project > My Projects' 카테고리의 다른 글
칸반보드 팀 프로젝트 회고 (2) 다시 만들어보기 - 구체화 (0) | 2024.05.23 |
---|---|
칸반보드 팀 프로젝트 회고 (1) 다시 만들어보기 - 구상편 (0) | 2024.05.23 |
메모장 만들기 02탄 : Spring, MySQL (0) | 2023.06.15 |
메모장 만들기 01탄 : Spring, MySQL (0) | 2023.06.14 |
호텔 예약 프로그램 만들기 : Only Java (0) | 2023.06.07 |