오늘은 최근 진행했던 프로젝트에서 테스트 코드를 작성한 방식과 그 결과에 대해 얘기하고자 한다. 참고로 당시 테스트 커버리지 수치가 아래와 같이 꽤 좋은 결과를 보였었다.
특히 Class에서는 100%, Method에서는 96% 등을 보이며 상당히 괜찮은 결과를 보였었다.
깃주소는 아래 참고
https://github.com/Curate-Me/claco-client
GitHub - Curate-Me/claco-client: 큐시즘 밋업데이 E조 큘미 Claco Client
큐시즘 밋업데이 E조 큘미 Claco Client. Contribute to Curate-Me/claco-client development by creating an account on GitHub.
github.com
일단 Spring의 계층 구조 부터 살펴보면 아래와 같다.
- Controller: 클라이언트 요청을 처리하고 서비스 계층과 통신하여 응답을 반환.
- Service: 비즈니스 로직을 처리하고 컨트롤러와 저장소 간의 중간 다리 역할.
- Repository: 데이터베이스와 직접 상호작용하며 데이터를 CRUD 처리.
- Domain: 비즈니스 데이터를 표현하는 객체로, 핵심 로직과 상태를 포함.
여기서 중요한 것은 우리가 테스트 코드를 짜는 것이 아니라, 애플리케이션을 직접 실행해서 테스트를 돌린다면 만일 오류가 발생하게되면 다음 총 4개의 계층에서 어떤 계층에서 문제가 생긴 것인지 알기가 매우 힘들다. 때문에 각 계층별로 테스트 코드를 작성해서 진행한다면 오류가 발생했을때 어디서 문제가 발생했는지 금방 쉽게 알 수 있다.
아무튼 이번 프로젝트에서는 총 두가지 레이어에서 테스트 코드를 작성하였다.
Service Layer: 단위 테스트
단위 테스트 (Unit Test)
- 목적: 개별 클래스 또는 메서드 단위의 동작을 테스트.
- 범위: 특정 클래스(주로 서비스, 레포지토리 등)만 독립적으로 테스트.
- 환경: Spring Context를 로드하지 않거나 최소한으로 로드.
- 속도: 빠름 (비용이 적음).
- 주요 도구:
- @MockBean, Mockito 등을 활용해 의존성을 모킹(Mock).
- @WebMvcTest (Controller 테스트용).
- @DataJpaTest (Repository 테스트용).
- 코드 예시:
@Slf4j
@ExtendWith(MockitoExtension.class)
class ClacoBookServiceTest {
@Mock
private MemberRepository memberRepository;
@Mock
private ClacoBookRepository clacoBookRepository;
@Mock
private SecurityContextUtil securityContextUtil;
@InjectMocks
private ClacoBookServiceImpl clacoBookService;
private final Long testId = 1L;
private final String testString = "test";
@Test
@DisplayName("ClacoBook 생성")
void createClacoBook() {
// Given
String bookColor = "#8F9AF8";
JwtMemberDetail jwtMemberDetailMock = mock(JwtMemberDetail.class);
Member testMember = Member.builder()
.id(testId)
.email("test@test.com")
.nickname("test")
.role(Role.MEMBER)
.socialId(testId)
.build();
when(securityContextUtil.getContextMemberInfo()).thenReturn(jwtMemberDetailMock);
when(jwtMemberDetailMock.getMemberId()).thenReturn(testId);
when(memberRepository.findMemberByIdWithClacoBook(testId)).thenReturn(Optional.of(testMember));
when(clacoBookRepository.save(any(ClacoBook.class))).then(AdditionalAnswers.returnsFirstArg());
UpdateClacoBookRequest request = UpdateClacoBookRequest.builder()
.color(bookColor)
.build();
// When
ClacoBookResponse result = clacoBookService.createClacoBook(request);
// Then
verify(securityContextUtil).getContextMemberInfo();
verify(jwtMemberDetailMock).getMemberId();
verify(memberRepository).findMemberByIdWithClacoBook(testId);
String bookTitle = "test님의 이야기";
assertThat(result.getId()).isNull();
assertThat(result.getTitle()).isEqualTo(bookTitle);
assertThat(result.getColor()).isEqualTo(bookColor);
}
위 코드는 ClacoBookServiceTest라는 테스트 클래스에서 createClacoBook 메서드를 단위 테스트(Unit Test)하는 코드이다.
테스트는 Mockito를 활용하여 의존성을 모킹(Mock)하고, 테스트 대상인 ClacoBookServiceImpl의 createClacoBook 메서드가 올바르게 동작하는지 검증하는 과정이다.
Repository: 통합 테스트
통합 테스트 (Integration Test)
- 목적: 여러 계층(Controller, Service, Repository 등)이 조화를 이루며 동작하는지 확인.
- 범위: 애플리케이션의 전반적인 흐름과 의존성 검증.
- 환경: Spring Context를 로드하고 실제 Bean을 사용.
- 속도: 상대적으로 느림 (비용이 높음).
- 주요 도구:
- @SpringBootTest: 전체 애플리케이션 컨텍스트 로드.
- TestRestTemplate, MockMvc: HTTP 요청 시뮬레이션.
- 내장 데이터베이스(H2 등)로 실제 데이터 흐름 검증.
@Slf4j
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class ClacoBookRepositoryTest {
@Autowired
EntityManager entityManager;
@Autowired
ClacoBookRepository clacoBookRepository;
@Test
void findClacoBookById() {
// Given
String testString = "test";
Member testMember = Member.builder()
.email("test@test.com")
.role(Role.MEMBER)
.socialId(1L)
.build();
entityManager.persist(testMember);
ClacoBook testBook = ClacoBook.builder()
.title(testString)
.color(testString)
.member(testMember)
.build();
entityManager.persist(testBook);
Long testId = testBook.getId();
// When
Optional<ClacoBook> result = clacoBookRepository.findClacoBookById(testId);
// Then
assertThat(result.isPresent()).isTrue();
assertThat(result.get().getMember()).isEqualTo(testMember);
assertThat(result.get().getTitle()).isEqualTo(testString);
}
}
위 코드는 JPA를 활용한 ClacoBookRepository의 findClacoBookById 메서드를 테스트하는 통합 테스트 코드이다.
@DataJpaTest 애너테이션을 사용하여 JPA와 관련된 컴포넌트만 로드하며, 테스트 데이터베이스를 사용하는 환경에서 동작하도록 설정하였다.
결론적으로 단위 테스트에서는 Mock 의존성 주입을 통해 동작 과정을 검증하는 거이고, 통합테스트에서는 @AutoWired를 통해 데이터베이스와 실제로 상호작용하며, 엔터티를 저장하고 조회하는 작업을 테스트하는 과정이다.
좀 더 자세히 표를 통해 비교해보면 아래와 같다.
구분 | 단위테스트 | 통합테스트 |
목적 | 개별 컴포넌트 동작 검증 | 전체 계층의 통합 동작 검증 |
범위 | 클래스 또는 메서드 단위 | 여러 계층 및 의존성 포함 |
Spring Context | 주로 로드하지 않음 | 전체 또는 일부 컨텍스트 로드 |
속도 | 빠름 | 느림 |
사용 사례 | 비즈니스 로직, 메서드 테스트 | API 엔드포인트, 데이터 흐름 테스트 |
도구 | Mockito, @MockBean | @SpringBootTest, MockMvc |
'Springboot' 카테고리의 다른 글
Spring Web MVC의 Dispatcher Servlet 파헤치기 (0) | 2025.04.05 |
---|---|
SpringBoot: TDD(Test-Driven-Development)란? (2) | 2025.01.03 |
Spring Batch 활용 (1) | 2024.12.09 |
Spring boot: DDD(Domain Driven Design)란? (0) | 2024.09.23 |
Spring boot 쿼리 최적화 문제와 Spring Cloud Openfeign (1) | 2024.06.19 |