728x90
Spring Test (JUnit, Mockito, Spring Boot)
옛날에 테스트라 함은...
노동 집약적인 테스트 진행
- 자동화된 테스트가 어려운 코드들이 많았음(SQL 중심의 코드 트리거, 패키지, 프로시저 등)
- 1회성 개발이 많았기 때문에(개발 후 철수), 코드의 품질보다는 기능적인 완성도만 체크를 했었습니다.
- FE/BE가 나눠져 있지 않고 서버에서 로직과 화면까지 모두 만들어서 보내는 구조가 많았기 때문에
테스트 자동화라고 해도 실제 화면에서 직접 마우스/키보드가 움직이는 것처럼 하는 테스트가 많았습니다.
현재의 테스트
- 자동화된 테스트
- 빠른 테스트
- 여러가지 테스트와 방법론 등 테스트의 발전
- 테스트가 중요하다는 것은 이미 너무 많은 사람들이 알고 있습니다.
테스트를 잘하면 좋은 점
- 테스트를 짜면서 나의 코드를 자연스럽게 셀프 코드 리뷰하게 됩니다.
- 테스트 하기가 어려운 경우 코드가 잘못된 것을 빨리 알아낼 수 있습니다.
예) 너무 다양한 일을 하고 역할이 많은 경우 - 테스트가 잘 되어 있으면 마음껏 리팩토링 할 수 있습니다.
1. 코드의 품질이 좋아집니다.
2. 현재 코드의 아주 세부적인 정책들이 모두 테스트로 문서화가 됩니다.
3. 다음에 기능을 추가하거나 변경할 때도 더 편하게 기존 기능의 영향도를 알 수 있고 안심하며 개발이 가능합니다.
결론 > 장기적으로 볼 때 더 빠르고 안정적인 개발이 가능하게 됩니다.
테스트를 잘 하려면
- 클래스나 메서드가 SRP를 지키고 너무 크지 않아야 합니다.
- 유닛 테스트의 경우 적절한 Mocking으로 격리성을 확보합니다.
- 테스트 커버리지를 높여서 테스트가 안되는 부분이 없도록 합니다.
JUnit
JUnit은 xUnit이라는 유닛테스트 프레임워크의 일환으로 Java용으로 개발된 프레임워크입니다.
JUnit 5
The JUnit team uses GitHub for version control, project management, and CI.
junit.org
- JUnit은 단위 테스트를 실행하고 결과를 검증해서 전체 결과를 리포트 해주는 프레임워크 입니다.
- 사용자가 직접 동작시킬 수도 있으며 Gradle이나 Maven 등을 통해 빌드하면서 테스트도 가능합니다.
- spring-boot-starter-test에 기본적으로 JUnit5가 포함되었습니다.
Mokito
이름과 비슷하게 Mock을 만들어 주는 라이브러리 입니다.
해결하고자 하는 점?
- 테스트하고자 하는 클래스가 의존하는 클래스를 모두 만들려다보니 테스트 만들기가 어렵습니다.
- 모든 클래스가 동작하다보면 어떤 부분이 문제인지 알기가 어렵습니다.
가짜(Mock)를 만들어서 내가 원하는 방식으로 동작하게 하자! Mockito 라이브러리 활용
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.BDDMockito.given;
@ExtendWith(MockitoExtension.class)
class AccountServiceTest {
@Mock
private AccountRepository accountRepository;
@InjectMocks
private AccountService accountService;
@Test
@DisplayName("계좌 조회 성공")
void getAccountTest() {
// given
given(accountRepository.findById(anyLong()))
.willReturn(Optional.of(Account.builder()
.accountStatus(AccountStatus.UNREGISTERED)
.accountNumber("65789")
.build())
);
// when
Account account = accountService.getAccount(4555L);
// then
assertEquals("65789", account.getAccountNumber());
assertEquals(AccountStatus.UNREGISTERED, account.getAccountStatus());
}
}
Controller 테스트 방법
컨트롤러를 테스트하는 2가지 대표적인 방법
1. @SpringBootTest + @AutoConfigureMockMvc
- 전체 Bean을 모두 생성한 후
- mockMvc를 통해 http 요청과 검증을 진행
2. @WebMvcTest
내가 필요로 하는 MVC관련 Bean들만 생성
- Controller, ControllerAdvice, Converter, Filter, HandlerInterceptor 등
- Service 등 Controller에서 의존하는 하위 레이어의 기능은 @MockBean을 통해 모킹해서 원하는 동작을 하도록 합니다.
(Mockito와 유사한 방식)
@WebMvcTest(AccountController.class)
class AccountControllerTest {
@MockBean
private AccountService accountService;
@MockBean
private RedisTestService redisTestService;
@Autowired
private MockMvc mockMvc;
@Test
void successGetAccount() throws Exception {
// given
given(accountService.getAccount(anyLong()))
.willReturn(Account.builder()
.accountNumber("3456")
.accountStatus(AccountStatus.IN_USE)
.build());
// when
// then
mockMvc.perform(MockMvcRequestBuilders.get("/account/876"))
.andDo(print())
.andExpect(jsonPath("$.accountNumber").value("3456"))
.andExpect(jsonPath("$.accountStatus").value("IN_USE"))
.andExpect(status().isOk());
}
}
Service 테스트 방법
verify
의존하고 있는 Mock이 해당되는 동작을 수행했는지 확인하는 검증입니다.
verify(accountRepository, times(1)).save(any<Account>());
verify(accountRepository, times(0)).findById(anyLong());
ArgumentCaptor
의존하고 있는 Mock에 전달된 데이터가 내가 의도하는 데이터가 맞는지 검증
ArgumentCaptor<Account> captor = ArgumentCaptor.forClass(Account.class); // 빈 박스 같은 것
verify(accountRepository, times(1)).save(captor.capture());
assetEquals("1234", captor.getValue().getAccountNumber());
Assertions
다양한 단언(assertion) 방법들
assertEquals("1234", captor.getValue().getAccountNumber());
assertNotEquals("1234", captor.getValue().getAccountNumber());
assertNull(result);
assertNotNull(result);
assertTrue(result.getBoolean());
assertFalse(result.getBoolean());
assertAll(
() -> assertTrue(result.getBoolean()),
() -> assertFalse(result.getBoolean())
);
* assertAll 위의 코드들 처럼 나열하면 중간에 검사 실패가 발생할 시 그 밑을 수행하지 않지만
assertAll을 사용할 시 전체를 항상 검사하니 다양한 결과를 한 눈에 볼 수 있습니다.
AssertThrows
예외를 던지는 로직을 테스트하는 방법
AccountException exception =
assertThrows(AccountException.class, () ->
accountService.getAccount(123L));
assertEquals(ACCOUNT_NOT_FOUND, exception.getErrorCode());
'Develop > Spring' 카테고리의 다른 글
JpaRepository 대신 Repository 를 사용해야 하는 이유 (0) | 2023.02.19 |
---|---|
Spring Boot ServletInitializer (0) | 2022.12.15 |
Spring Exception (0) | 2022.11.19 |
Spring Filter, Interceptor (0) | 2022.11.17 |
Spring MVC (0) | 2022.11.17 |