요구사항
- 가게 운영 시간(10:00 ~ 22:00) 외에는 주문을 생성할 수 없다.
테스트 하기 어려운 영역이란?
- 관측할 때마다 다른 값에 의존하는 코드
- 현재 날짜/시간, 랜덤 값, 전역 변수/함수, 사용자 입력 등
- 외부 세계에 영향을 주는 코드
- 표준 출력, 메세지 발송, 데이터베이스에 기록하기 등
- "현재 가게 운영 시간 외에는 주문을 생성할 수 없다" 라는 요구사항은 현재 시간이 계속 변경되기 때문에 관측할 때마다 다른 값에 의존하는 코드라고 볼 수 있다.
테스트 하기 쉬운 영역이란?
- 순수함수(pure functions)
- 같은 입력에는 항상 같은 결과
- 외부 세상과 단절된 형태(DB)
- 테스트하기 쉬운 코드
구현코드
private static final LocalTime SHOP_OPEN_TIME = LocalTime.of(10, 0);
private static final LocalTime SHOP_CLOSE_TIME = LocalTime.of(22, 0);
public final static String NOT_ORDER_TIME = "주문 시간이 아닙니다. 관리자에게 문의하세요.";
처음 구상 (테스트 코드를 생각 하지 않고)
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(NOT_ORDER_TIME);
}
return new Order(LocalDateTime.now(), beverages);
}
- 현재 날짜(시간)를 LocalDateTime.now()를 통해 받아온다.
- 현재 시간을 10~22시 안에 속하는지 확인 후 범위에서 벗어나게 된다면 예외를 발생시킨다.
테스트하기 쉬운 코드일까?
@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시와 22시 이외의 시간이 입력된다면, 이 테스트 코드는 실패하는 코드로
- 위에 설명했듯이 테스트 하기 어려운 케이스에 속한다.
해결책
public Order createOrder(LocalDateTime currentDateTime) {
LocalTime currentTime = currentDateTime.toLocalTime();
if (currentTime.isBefore(SHOP_OPEN_TIME) || currentTime.isAfter(SHOP_CLOSE_TIME)) {
throw new IllegalArgumentException(NOT_ORDER_TIME);
}
return new Order(LocalDateTime.now(), beverages);
}
- 현재 시간을 매개변수로 받아와서 비교를 진행하는 방식으로 변경하였다.
- 입력받아 비교를 진행하기 때문에 시간에 따라 일정한 결과를 예상할 수 있게 되었다.
@Test
void createOrderWithCurrentTime() {
CafeKiosk cafeKiosk = new CafeKiosk();
Americano americano = new Americano();
cafeKiosk.add(americano);
Order order = cafeKiosk.createOrder(LocalDateTime.of(2023,1,17,14,0));
assertThat(order.getBeverages()).hasSize(1);
assertThat(order.getBeverages().get(0).getName()).isEqualTo("아메리카노");
}
@Test
void createOrderWithOutsideOpenTime() {
CafeKiosk cafeKiosk = new CafeKiosk();
Americano americano = new Americano();
cafeKiosk.add(americano);
assertThatThrownBy(() -> cafeKiosk.createOrder(LocalDateTime.of(2023,1,17,9,59)))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage(NOT_ORDER_TIME);
}
- 첫번째 코드는 성공 테스트를 의미하고
- 두번째 코드는 실패 케이스를 의미한다.
- 이전의 코드는 결과를 예상하기 힘들었다. 현재 시간이 계속 변했기 때문에!
- 외부로 값을 분리하면 테스트 하기 어려운 영역을 테스트할 수 있다.
'Spring관련 기술 > 테스트코드' 카테고리의 다른 글
@DisplayName (0) | 2023.12.12 |
---|---|
Test Driven Development (0) | 2023.12.11 |
테스트 케이스 세분화하기 (0) | 2023.12.10 |
단위 테스트(Unit test) (0) | 2023.12.10 |
CafeKiosk 요구사항 및 수동 테스트 (0) | 2023.12.10 |