테스트 코드는 코드 품질을 위해 선택이 아닌 필수로 진행해야 하는 과정 중 하나이다.
개발자는 다양한 시나리오에서 코드가 예상대로 작동하는지 확인하여 소프트웨어 품질을 유지하고 향상시킬 수 있다.
또한 새로운 요구사항으로 기존 코드가 수정되더라도 테스트 코드를 이용해 빠른 시간 안에 버그를 발견할 수 있고 기존 기능이 그대로 잘 동작하는지 확인이 가능하다.
Given - When - Then 패턴
Junit5
- 자바(Java) 언어에서 사용되는 단위 테스트 프레임워크
- Junit5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- @BeforeAll, @BeforeEach, @Test, @AfterEach, @AfterAll 제공
Mock
Mockito
- Mock = 가짜
- Mockito는 Mock 객체를 쉽게 만들고, 관리하고, 검증할 수 있는 방법을 제공하는 프레임워크
- Mock을 기반으로 작성된 테스트 코드는 인터넷이 안되는 비행기 안에서도 동작 및 검증 가능
- 통합테스트인 @SpringBootTest 와 다르게 별도 환경, 구성에 대한 제약 사항이 없기 때문에 매우 빠르게 동작
Unit Test

- 단위(Unit) 테스트에서 말하는 단위(Unit)는 애플리케이션에서 가장 작은 테스트 가능 요소를 뜻함
→ 함수(Method) - 실제 객체를 사용하지 않고 Mocking을 함으로써 의존성이 적고 빠르게 테스트를 할 수 있음
- FIRST 원칙
-
- Fast: 유닛 테스트는 빨라야 함
- Isolated: 테스트는 각 테스트간에 독립적으로 실행해야함
- Repeatable: 테스트는 환경에 상관없이 실행할 때마다 같은 결과를 만들어야 함.
- Self-validating: 테스트는 명확히 성공/실패로 구분하여 테스트 자체가 결과를 검증할 수 있어야함.
- Timely: 테스트는 개발간에 즉시 작성해야 함. 대표적으로 TDD 방법론이 있음. (쉽지 않음)
-
Mock Unit Test
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.times;
import static org.mockito.BDDMockito.given;
@ExtendWith(MockitoExtension.class)
public class MovieServiceTest {
@Mock
MovieRepository movieRepository;
@InjectMocks
MovieService movieService;
@Test
@DisplayName("영화 단건조회 테스트")
public void getMovieTest() {
// given
int movieId = 1;
Movie movie = new Movie(
new Director("ahn"),
List.of(new Actor("park"), new Actor("kim"))
);
given(movieRepository.findById(anyLong())).willReturn(Optional.of(movie));
// when
MovieResponse result = movieService.getMovie(movieId);
// then
assertNotNull(result);
}
}
Mocking
@ExtendWith(MockitoExtension.class)
public class MovieServiceTest {
@Mock
MovieRepository movieRepository;
@InjectMocks
MovieService movieService;
@Test
@DisplayName("영화 단건조회 테스트")
public void getMovieTest() {
...
}
}
- @Mock: 테스트에서 사용될 가짜 객체. 해당 객체의 메서드를 모방 하기 위한 용도
- @InjectMocks: Mock 객체를 주입하여 실제 객체의 의존성을 자동으로 설정함. Mock이 아닌 실제 객체.
- Mocking은 실제 객체 대신 가짜 객체를 만들어 테스트 중에 해당 객체의 동작을 시뮬레이션하는 과정
Class/Method Naming
@Test
@DisplayName("displayName 영화 단건 조회")
public void getMovieTest() {
...
}
@Test
public void 함수명_영화_단건조회() {
...
}
- 클래스명은 {TargetClass}Test 형태로 작성하는 편
- 함수명
-
- @DisplayName을 사용하고 함수명을 영문 테스트명을 작성
- @DisplayName을 사용하지 않고 한글로 테스트명을 작성(언더바 처리가 필요)
- 둘다 자주 사용하는 방식이고, 정답은 없음. 개발간에는 취향따라, 실무에서는 회사 또는 팀 규정따라.
given
@Test
public void 영화_단건조회() {
// given
long movieId = 1L;
given(movieRepository.findById(anyLong())).willReturn(Optional.of(movie));
...
}
- given에서 자원을 주어줄 때, mock에 어떤 값이든(any) 할당하는 값 메소드
- willRetrun으로 결과 값을 매핑하기 때문에 자원에는 어떠한 값이 매핑되어도 상관 없기 때문에 사용함
- any(), anyLong(), anyString(), anyList(), anySet(), anyMap() 등이 있음
when
@Test
@DisplayName("영화 단건조회 테스트")
public void getMovieTest() {
...
// when
MovieResponse result = movieService.getMovie(movieId);
...
}
- @InjectMocks 대상 객체는 mock 객체가 아님
- @InjectMocks 대상은 테스트하려는 실제 객체를 생성하고, 그 객체의 의존성들만 mocking된 객체로 대체하는 것
- 따라서 @InjectMocks 대상 객체에는 mock에 넣어주는 any() 가 아닌 실제 자원을 넣어줘야함
then
@Test
@DisplayName("영화 삭제 실패")
public void getMovieTest() {
...
// then
assertEquals("entity null error", exception.getMessage());
verify(movieRepository, times(0)).delete(any(Movie.class));
}
- assert: 테스트의 결과를 검증하는 데 사용하며, 테스트 예상한 결과와 실제 결과와 비교하여 테스트가 성공/실패를 결정
- verify: 특정 메소드가 호출되었는지, 호출 횟수는 몇 번인지, 호출 순서는 어떤지 등을 검증하는 데 사용
throws
@Test
public void 영화단건_조회() {
// given
long movieId = 1L;
given(movieRepository.findById(anyLong())).willReturn(Optional.empty());
// when
NullPointerException exception = assertThrows(NullPointerException.class, () -> movieService.getMovie(movieId));
// then
assertEquals("null error", exception.getMessage());
}
- assertThrows 로 처리하며, 각 상황에 정의한 에러 메세지가 맞는지 확인함
void method
@Test
public void 영화_삭제() {
// given
long movieId = 1L;
Movie movie = new Movie("재밌는영화", 2002);
given(movieRepository.findById(anyLong())).willReturn(Optional.of(movie));
doNothing().when(movieRepository).delete(any(Movie.class));
// when
movieService.removeMovie(movieId);
// then
verify(movieRepository, times(1)).delete(any(Movie.class));
}
- Mocktito.doNothing 메소드로 아무것도 처리 하지 않겠다는 의미
- verify와 함께 핵심 자원만 호출 되었는지 정도 확인 해주면 좋음
spy
import org.mockito.Spy;
@ExtendWith(MockitoExtension.class)
public class MovieServiceTest {
@Mock
MovieRepository movieRepository;
@Spy
private LogService logService;
@InjectMocks
MovieService movieService;
@Test
@DisplayName("영화 단건조회 테스트")
public void getMovieTest() {
...
}
}
- @Spy: 마치 첩보 요원 처럼, 실제 객체의 행동을 하면서 우리가 조작한 행동(stubbing)은 우리가 설정한대로 동작
- 실제 객체를 사용하면서 일부 기능에 대한 mock 처리가 필요할 때 사용
Nested
import org.junit.jupiter.api.Nested;
@ExtendWith(MockitoExtension.class)
public class MovieServiceTest {
...
@Nested
class GetMovieTest {
@Test
public void 영화단건조회_조회결과없음() {
...
}
@Test
public void 영화단건조회_정상조회() {
...
}
}
@Nested
class DeleteMovieTest {
@Test
public void 영화삭제_조회결과없음() {
...
}
@Test
public void 영화삭제_정상동작() {
...
}
}
}

- @Nested 중첩 클래스를 통해 테스트 묶음 단위 설정 가능
- 서로 관련 있는 Unit 테스트간 묶음을 통해 테스트 코드 가독성 증가
- 묶음 단위 내에서의 순서를 통해 테스트 시나리오 작성 가능
WebMvc Test
WebMvc


@WebMvcTest bean 목록(공식)
i.e. @Controller, @ControllerAdvice, @JsonComponent, Converter/GenericConverter, Filter, WebMvcConfigurer and HandlerMethodArgumentResolver beans but not @Component, @Service or @Repository beans.
- @WebMvcTest는 전체 테스트 하는 것이 아니라 Web layer만을 테스트할 때 사용
Controller Test
@WebMvcTest(MovieController.class)
public class MovieControllerTest {
@MockBean
private MovieService movieService;
@Autowired
private MockMvc mockMvc;
@Test
public void 영화_단건_조회() throws Exception {
// given
long movieId = 1L;
given(movieService.getMovie(anyLong())).willReturn(new MovieResponse(1L, "재밌는영화", 1992));
// when
ResultActions result = mockMvc.perform(get("/api/v1/movies/{movieId}", movieId));
// then
result.andExpect(status().isOk());
}
}
- @MockBean: Spring 컨텍스트에서 관리되는 Bean을 mock 객체로 교체함
- 특정 컨트롤러나 웹 계층에 대한 단위 테스트 진행
- 컨트롤러가 예상대로 작동하는지, 웹 요청과 응답이 제대로 이루어지는지 검증
SpringBoot Test
@SpringBootTest
public class MovieServiceTest {
@Autowired
MovieService movieService;
@Test
@DisplayName("영화 단건조회 테스트")
public void getMovieTest() {
// given
int movieId = 1;
// when
MovieResponse movie = movieService.getMovie(movieId);
// then
assertNotNull(movie);
}
}
- SpringBoot의 Integration Test (통합 테스트)
- 애플리케이션이 실제로 실행될 때처럼 빈(Bean)들이 초기화되고 설정된 환경에서 테스트를 수행
- 실제 설정한 환경 구성에 따른 실제 데이터를 사용하기 때문에 실제로 등록된 데이터에 대한 실제 로직이 수행됨
Test Coverage

- 애플리케이션의 테스트 케이스가 얼마나 충족되었는지를 나타내는 지표
- 커버리지가 항상 100%가 될 필요는 없음. 사실상 불가함.
- 커버리지 자체가 목적이 되기보다는, 중요한 로직과 엣지 케이스에 대한 충분한 테스트가 필요
- 커버리지가 높다고 해서 항상 코드가 완벽하다는 보장은 없으며, 테스트의 품질이 더 중요
'내배캠 > TIL' 카테고리의 다른 글
[SQL] 오프라인/온라인 판매 데이터 통합하기 (1) | 2024.09.14 |
---|---|
트랜잭션 격리 수준 (0) | 2024.09.12 |
[스프링] AOP (0) | 2024.09.10 |
카카오 로그인 개발하기 (5) | 2024.09.09 |
JPA의 N+1 문제 해결하기 (0) | 2024.09.06 |