늦은 프로그래밍 이야기
Spring Test 본문
Spring Test
테스트 코드가 왜 필요한가
궁극적 목표
테스트 코드를 작성하는 이유는 잘 동작하는 깔끔한 코드를 얻기 위해서이다.
테스트 코드를 작성하면 깔끔한 코드를 얻을 수 있는 이유
테스트를 쉽게하기 위해서는, 어플리케이션 코드를 테스트하기 쉽게 짜야한다. 결국 테스트 코드를 짜기 위해 노력하다 보면 어플리케이션 코드가 깔끔해진다.시간의 단축
테스트 코드를 작성하면 시간이 되려 오래 걸릴 것 같지만, 오히려 시간을 단축할 수 있다.
테스트 코드 작성 전 테스트 과정
- 코드를 수정한다.- 서버를 동작시킨다.
- 필요에 따라 테스트에 필요한 데이터를 DB에 입력한다.
- 브라우저를 통해 서버에 접속하고, 테스트 대상 메소드를 동작시키는 요청을 한다.
- 테스트를 마치고, DB의 데이터를 정리한다. (h2는 자동으로 해줌)
- 이 과정을 반복한다.
알 수 없는 오류로 실패하는 경우 계속해서 테스트를 위해 위 과정을 반복하게 된다. 매우 귀찮고, 시간이 오래 걸린다.
테스트 코드 작성 후 테스트 과정
- 코드를 수정한다.- 테스트 코드를 실행한다. (테스트 코드를 이미 작성)
- 결과를 확인한다.
테스트 코드를 작성한다면 코드 수정 후 할 일은 테스트 코드를 실행하는 일 뿐이다. 테스트 코드를 통해서 실행이 성공했는지 실패했는지 알 수 있기 때문에 매우 많은 시간을 절약할 수 있다.
장점
- 서버를 실행하는 등의 시간을 절약할 수 있다.
- 필요한 데이터를 미리 기입하고, 테스트가 끝나고 정리하는 등의 행동을 하지 않아도 된다.
- 단위 테스트의 경우 수십 ms이기 때문에 테스트가 매우 빠르다.
- 문서로서의 역할이 가능하다. → 테스트 코드는 개발자가 작성한 메소드가 어떻게 동작 했으면, 어떤 결과를 반환 했으면, 하는 것을 작성한 것이기 때문에 처음 코드를 보는 개발자들이 테스트 코드를 통해서 코드의 동작을 조금 더 수월하게 이해할 수 있다.
- 깔끔한 인터페이스를 얻어낼 수 있다.
테스트 코드의 작성 방법
단위 테스트 (Unit Test)
단위 테스트 (Unit Test)는 컴퓨터 프로그래밍에서 소스코드의 특정 모듈이 의도된 대로 정확히 작동하는지 검증하는 절차이다. 즉, 모든 함수와 메소드에 대한 테스트 케이스(
Test case)를 작성하는 절차를 말한다.
이를 통해서 언제라도 코드 변경으로 인해 문제가 발생할 경우, 단시간 내에 이를 파악하고 바로 잡을 수 있도록 해준다. 각 테스트 케이스는 서로 분리되어야 한다. 이를 위해 가짜 객체 (Mock object)를 생성하기도 한다.
프로그램을작은 단위로 쪼개서 각 단위가 정확하게 동작하는지 검사하고 이를 통해 문제 발생시 정확하게 어느 부분이 잘못 되었는지를재빨리 확인할 수 있다.
장점
문제점 발견
프로그램의 안정성이 높아진다. 디버깅 시간을 단축시킴으로써 시간을 단축하여 준다.변경이 쉽다
단위 테스트를 믿고 리팩토링 할 수 있다. 리팩토링 후에도 해당 모듈이 의도대로 작동하고 있음을 단위 테스트를 통해서 확신할 수 있다. 어떻게 수정하든 문제점을 금방 파악할 수 있고 수정된 코드가 정확하게 동작하는지 쉽게 알 수 있다.통합이 간단하다
단위 테스트는 유닛 자체의 불확실성을 제거해주므로 상향식(bottom-up) 테스트 방식에서 유용하다. 먼저 프로그램의 각 부분을 검증하고 그 부분들을 합쳐서 다시 검증하는 통합 테스트에서 빛을 발한다.TDD (Test-Driven Development)
테스트 주도 개발(TDD)은 매우 짧은 개발 사이클을 반복하는 소프트웨어 개발 프로세스 중 하나이다. 테스트 코드를 먼저 작성하고 실제 동작하는 코드를 개발하는 순서로 개발하는 방법론
순서
- 개발자는 먼저 요구사항을 검증하는 자동화 된 테스트 케이스를 작성한다.
- 그 테스트 케이스를 통과하기 위한 최소한의 코드를 생성한다.
- 마지막으로 작성한 코드를 표준에 맞도록 리팩토링 한다.
Give - When - Then Pattern
테스트 코드를 작성하는 가장 대표적인 방법론이다. 단계별로 테스트 코드를 나누어서 직관적으로 작성할 수 있다.
- Give (준비)
- When (실행)
- Then (검증)
예시
// (1)
class ProductTest {
@Test // (2)
@DisplayName("정상 케이스") // (3)
void createProduct_Normal() {
// (4) given - 준비
Long userId = 100L;
String title = "오리온 꼬북칩 초코츄러스맛 160g";
String image = "https://shopping-phinf.pstatic.net/main_2416122/24161228524.20200915151118.jpg";
String link = "https://search.shopping.naver.com/gate.nhn?id=24161228524";
int lprice = 2350;
ProductRequestDto requestDto = new ProductRequestDto(
title,
image,
link,
lprice
);
// (5) when - 테스트하려는 로직 수행!
Product product = new Product(requestDto, userId);
// (6) then - 검증!
assertNull(product.getId()); // (6-a)
assertEquals(userId, product.getUserId()); // (6-b)
assertEquals(title, product.getTitle());
assertEquals(image, product.getImage());
assertEquals(link, product.getLink());
assertEquals(lprice, product.getLprice());
assertEquals(0, product.getMyprice());
}
}
- 인텔리제이 Constructor 메뉴에서 test를 선택 후 기본 옵션으로 생성하면 자동으로 테스트 코드 클래스가 생성된다.
- 테스트 클래스의 메소드가 하나의 테스트 케이스를 나타낸다. (@Test)
- 테스트에 라벨링을 해주는 어노테이션
- 데이터를 준비하는 Given
- 실제로 테스트 해야 하는 로직을 실행
- JUnit에서 제공하는 검증 함수
a. assertNull : 주어진 인자(product.getId())가 null이어야 테스트를 통과 시켜준다.
b. assertEquals : 주어진 인자 두개가 같아야 테스트를 통과 시켜준다.
Mock Object
각각의 테스트 케이스를 서로 분리하기 위해서 가짜객체 (Mock object)를 생성한다.

예를들어, Sevice 클래스는 Controller와 Repository와 의존하고 있기 때문에 오류가 발생했을 때 이 오류가 Service에서 발생한 오류인지 Repository에서 발생한 오류인지 판단하기가 어려워진다. 따라서 Repository의 Mock object를 생성하여 미리 설정해 둔 더미 데이터만 반환하게 한다면 Service 로직만 분리하여 테스트 할 수 있다.
예시
@ExtendWith(MockitoExtension.class)
class ProductServiceTest {
@Mock // (1)
ProductRepository productRepository;
@InjectMocks // (2)
ProductService productService;
@Mock
User user;
@Test
@DisplayName("관심 상품 희망가 - 최저가 이상으로 변경")
void updateProduct_Success() {
// given
Long productId = 100L;
int myprice = MIN_MY_PRICE + 100;
Long userId = 777L;
ProductMypriceRequestDto requestMyPriceDto = new ProductMypriceRequestDto(
myprice
);
ProductRequestDto requestProductDto = new ProductRequestDto(
"오리온 꼬북칩 초코츄러스맛 160g",
"https://shopping-phinf.pstatic.net/main_2416122/24161228524.20200915151118.jpg",
"https://search.shopping.naver.com/gate.nhn?id=24161228524",
2350
);
Product product = new Product(requestProductDto, userId);
// (3)
when(user.getId())
.thenReturn(userId);
when(productRepository.findByIdAndUserId(productId, userId))
.thenReturn(Optional.of(product));
// when, then
assertDoesNotThrow( () -> {
productService.updateProduct(productId, requestMyPriceDto, user);
});
}
- @Mock 어노테이션으로 모킹할 객체를 표기해준다.
- @InjectMock 어노테이션으로 모킹한 객체를 주입해주는 코드
- when() 메소드를 통해 모킹한 객체들이 특정 조건으로 특정 메소드를 호출하면 일괄적으로 다음과 같이 동작하도록 지정해준다.
'내일배움캠프 > Spring' 카테고리의 다른 글
| POJO (0) | 2022.12.23 |
|---|---|
| IoC 컨테이너 (0) | 2022.12.21 |
| DI (의존성 주입) (0) | 2022.12.21 |
| Annotation (1) | 2022.12.21 |
| UnsatisfiedDependencyException 에러 해결 (0) | 2022.12.16 |