우선 프로젝트를 아래와 같은 요구사항으로 세팅하고 테스트를 진행해 보자.
- 주문 목록에 음료 추가/삭제 기능
- 주문 목록 전체 지우기
- 주문 목록 총 금액 계산하기
- 주문 생성하기
수동 테스트 vs 자동화 테스트
class CafeKioskTest {
@Test
void add() {
CafeKiosk cafeKiosk = new CafeKiosk();
cafeKiosk.add(new Americano());
System.out.println(">>> 담긴 음료 수 : " + cafeKiosk.getBeverages().size());
System.out.println(">>> 담긴 음료 : " + cafeKiosk.getBeverages().get(0).getName());
}
}
- 위의 코드는 자동화된 테스트 코드가 아니다. 사람이 직접 출력을 보고 판단해야 하는 수동적인 방식이다.
- 다른 사람이 테스트 코드를 접했을 때, 테스트가 맞는지 틀린 지 확인하기 어렵다.
=> 수동이 아닌 자동화된 테스트가 필요하다.
JUnit 5 테스트
단위 테스트
작은 코드 단위(클래스 또는 메서드)를 독립적으로 검증하는 테스트로 검증 속도가 빠르고 안정적이다.
JUnit 5
단위 테스트를 위한 테스트 프레임워크
AssertJ
테스트 코드 작성을 돕는 라이브러리로 풍부한 API, 메서드 체이닝 지원한다.
🔻 Jnuit, AssertJ 사용한 테스트
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
class AmericanoTest {
@Test
void getName() {
Americano americano = new Americano();
//junit 사용
// assertEquals(americano.getName(), "아메리카노");
//assertj 사용
assertThat(americano.getName()).isEqualTo("아메리카노");
}
@Test
void getPrice() {
Americano americano = new Americano();
assertThat(americano.getPrice()).isEqualTo(4000);
}
}

- 출력을 확인해야 하는 테스트와 다르게 성공과 실패 여부를 구분할 수 있다.
테스트 케이스 세분화
해피 케이스 - 요구사항에 나오는 케이스
예외 케이스 - 요구사항에 드러나지 않은 케이스
=> 경계값(범위, 구간, 날짜 등) 테스트 진행이 필요하다.
경계값 테스트를 하기 위해 요구사항에 한 종류의 음료 여러 잔을 한 번에 담는 기능을 추가해 보자.
public class CafeKiosk {
private final List<Beverage> beverages = new ArrayList<>();
//수량 추가한 코드
public void add(Beverage beverage, int count) {
if (count <= 0) {
throw new IllegalArgumentException("음료는 1잔 이상 주문하실 수 있습니다.");
}
for (int i = 0; i < count; i++) {
beverages.add(beverage);
}
}
}
🔻 테스트 케이스 세분화 - 경계값 테스트
class CafeKioskTest {
//2잔으로 주문한 경우
@Test
void addSeveralBeverages() {
CafeKiosk cafeKiosk = new CafeKiosk();
Americano americano = new Americano();
cafeKiosk.add(americano, 2);
assertThat(cafeKiosk.getBeverages().get(0)).isEqualTo(americano);
assertThat(cafeKiosk.getBeverages().get(1)).isEqualTo(americano);
}
//0잔으로 주문한 경우
@Test
void addZeroBeverages() {
CafeKiosk cafeKiosk = new CafeKiosk();
Americano americano = new Americano();
assertThatThrownBy(() -> cafeKiosk.add(americano, 0))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("음료는 1잔 이상 주문하실 수 있습니다.");
}
}
테스트하기 어려운 영역 분리하기
관측할 때마다 다른 값에 의존하는 코드 - 현재 날짜/시간, 랜덤 값, 전역 변수/함수, 사용자 입력 등
외부 세계에 영향을 주는 코드 - 표준 출력, 메시지 발송, 데이터베이스에 기록하기 등
테스트하기 어려운 영역을 이해하기 위해 요구사항에 가게 운영 시간(10:00 ~ 22:00)외에는 주문을 생성할 수 없다는 조건을 추가해 보자.
public class CafeKiosk {
public static final LocalTime SHOP_OPEN_TIME = LocalTime.of(10, 0);
public static final LocalTime SHOP_CLOSE_TIME = LocalTime.of(22, 0);
public Order createOrder() {
LocalDateTime currentDateTime = LocalDateTime.now();
LocalTime currentTime = currentDateTime.toLocalTime();
if (currentTime.isBefore(SHOP_OPEN_TIME) || currentTime.isAfter(SHOP_CLOSE_TIME)) {
throw new IllegalArgumentException("주문 시간이 아닙니다. 관리자에게 문의하세요.");
}
return new Order(currentDateTime, beverages);
}
}
🔻 주문 테스트
class CafeKioskTest {
@Test
void createOrder() {
CafeKiosk cafeKiosk = new CafeKiosk();
Americano americano = new Americano();
cafeKiosk.add(americano);
Order order = cafeKiosk.createOrder();
assertThat(order.getBeverages()).hasSize(1);
assertThat(order.getBeverages().get(0).getName()).isEqualTo("아메리카노");
}
}
- 실제로 오후 10시 넘은 시간에 코드를 돌려봤는데 가게 운영 시간 범위를 벗어나서 테스트는 실패한다.
- 물론, 가게 운영 시간 안에 테스트를 진행한다면 성공할 것이다.
=> 테스트할 때 제어할 수 있도록 시간을 파라미터로 받고 경계값 테스트를 진행해줘야 한다.
🔻 시간을 파라미터로 받음
public class CafeKiosk {
public static final LocalTime SHOP_OPEN_TIME = LocalTime.of(10, 0);
public static final LocalTime SHOP_CLOSE_TIME = LocalTime.of(22, 0);
public Order createOrder(LocalDateTime currentDateTime) {
LocalTime currentTime = currentDateTime.toLocalTime();
if (currentTime.isBefore(SHOP_OPEN_TIME) || currentTime.isAfter(SHOP_CLOSE_TIME)) {
throw new IllegalArgumentException("주문 시간이 아닙니다. 관리자에게 문의하세요.");
}
return new Order(currentDateTime, beverages);
}
}
🔻 주문 테스트 - 경계값 테스트
class CafeKioskTest {
@Test
void createOrderWithCurrentTime() {
CafeKiosk cafeKiosk = new CafeKiosk();
Americano americano = new Americano();
cafeKiosk.add(americano);
Order order = cafeKiosk.createOrder(LocalDateTime.of(2024, 10, 3, 10, 0));
assertThat(order.getBeverages()).hasSize(1);
assertThat(order.getBeverages().get(0).getName()).isEqualTo("아메리카노");
}
//운영 시간이 아닌 경우
@Test
void createOrderOutsideOpenTime() {
CafeKiosk cafeKiosk = new CafeKiosk();
Americano americano = new Americano();
cafeKiosk.add(americano);
assertThatThrownBy(() -> cafeKiosk.createOrder(LocalDateTime.of(2024, 10, 3, 9, 59)))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("주문 시간이 아닙니다. 관리자에게 문의하세요.");
}
}
- 테스트 코드는 개발자가 제어할 수 있는 값을 넣어 확인한다.
✨👩💻✨
간단한 예시를 통해 테스트 케이스를 세분화하고, 어려운 영역을 구분해야 하는 이유를 확실하게 알 수 있어서 좋았다.
앞으로 테스트가 필요한 부분을 명확히 구분하고, 경계값 테스트를 통해 코드의 신뢰성을 높이는 데 집중해야겠다.
출처
'Study > Spring' 카테고리의 다른 글
| [Test] 테스트의 필요성 (0) | 2024.09.26 |
|---|---|
| [Spring] API 예외 처리 (0) | 2024.09.11 |
| [Spring] 서블릿 예외 처리 (0) | 2024.09.05 |
| [JPA] N+1 문제 원인 및 해결 (0) | 2024.09.03 |
| [Spring] 로그인 처리하기 - 쿠키, 세션 (0) | 2024.06.04 |