💡 기본적인 테스트 개념에 대해 알아보고 Spring에서 사용하는 테스트 방법들에 대해 정리해보고자 한다.
그 외에도 인수 테스트(시나리오 테스트), E2E테스트 (EndToEnd 테스트), 스트레스 테스트(부하 테스트) 등이 있지만, 이에 대해선 아직 접해본 적이 없기에 다음에 다뤄볼 기회가 생기면 그때 정리를 해보려 한다.
오늘은 스프링에서 사용되는 단위, 통합 테스트에 대해 간략히 알아보자
스프링에서 단위, 통합 테스트를 어떻게 활용하는지 알아보기에 앞서 테스트에 관해 이야기 할 때 항상 나오는 Mock과 Stub에 대해 간략히 알아보자
@ExtendWith(MockitoExtension.class)
public class MockTests {
@Mock
private OrderRepository orderRepository;
@InjectMocks
private OrderService orderService;
}
/**************/
@SpringBootTest
public class MockTests {
@MockBean
private OrderRepository orderRepository;
@Autowired
private OrderService orderService;
}
public class PhoneBookService {
private PhoneBookRepository phoneBookRepository;
public void register(String name, String phone) {
if(!name.isEmpty() && !phone.isEmpty()
&& !phoneBookRepository.contains(name)) {
phoneBookRepository.insert(name, phone);
}
}
public String search(String name) {
if(!name.isEmpty() && phoneBookRepository.contains(name)) {
return phoneBookRepository.getPhoneNumberByContactName(name);
}
return null;
}
}
/***********************/
// Tranditional Mockito
// given
when(phoneBookRepository.contains(momContactName))
.thenReturn(false);
// doReturn(false).when(phoneRepository).contains(momContactName)
// when
phoneBookService.register(momContactName, momPhoneNumber);
// then
verify(phoneBookRepository)
.insert(momContactName, momPhoneNumber);
/*
테스트 대상의 repository가 contains 메서드를 실행하면
false만 반환하는 상태에서 출발하여(Given)
register 메서드를 실행했을 때
insert 메서드가 한번 실행되어야 함
Tranditional Mockito는 given 부분에서 when 메서드를 사용하여
문맥상 혼란이 있을 수 있음
이를 해결하기 위하여 BDD Mockito를 통해 아래와 같이 수정
코드의 로직은 동일
*/
/***********************/
// BDDMockito
// given
given(phoneBookRepository.contains(momContactName))
.willReturn(false);
// when
phoneBookService.register(momContactName, momPhoneNumber);
// then
then(phoneBookRepository)
.should()
.insert(momContactName, momPhoneNumber);
/***********************/
// Returning a Fixed Value
given(phoneBookRepository.contains(momContactName))
.willReturn(false);
phoneBookService.register(xContactName, "");
then(phoneBookRepository)
.should(never())
.insert(momContactName, momPhoneNumber);
/***********************/
// Returning a Dynamic Value
given(phoneBookRepository.contains(momContactName))
.willReturn(true);
given(phoneBookRepository.getPhoneNumberByContactName(momContactName))
.will((InvocationOnMock invocation) ->
invocation.getArgument(0).equals(momContactName)
? momPhoneNumber
: null);
phoneBookService.search(momContactName);
then(phoneBookRepository)
.should()
.getPhoneNumberByContactName(momContactName);
/***********************/
// Throwing an Exception
given(phoneBookRepository.contains(xContactName))
.willReturn(false);
willThrow(new RuntimeException())
.given(phoneBookRepository)
.insert(any(String.class), eq(tooLongPhoneNumber));
try {
phoneBookService.register(xContactName, tooLongPhoneNumber);
fail("Should throw exception");
} catch (RuntimeException ex) { }
then(phoneBookRepository)
.should(never())
.insert(momContactName, tooLongPhoneNumber);
- 사용자(클라이언트)가 URL을 입력하면 HTTP Request가 Servlet Container로 전송합니다.
- 요청을 전송받은 Servlet Container는 HttpServletRequest, HttpServletResponse 객체를 생성합니다.
- web.xml을 기반으로 사용자가 요청한 URL이 어느 서블릿에 대한 요청인지 찾습니다.
- 해당 서블릿에서 service메소드를 호출한 후 클리아언트의 GET, POST여부에 따라 doGet() 또는 doPost()를 호출합니다.
- doGet() or doPost() 메소드는 동적 페이지를 생성한 후 HttpServletResponse객체에 응답을 보냅니다.
- 응답이 끝나면 HttpServletRequest, HttpServletResponse 두 객체를 소멸시킵니다.