
테스트 코드란?
production code
: 사용자가 실제로 사용하는 소스 코드
test code
: production code 가 정상적으로 동작하는지 확인하는 코드
테스트 코드가 왜 필요해요? 사람이 하면 되능거 아닌가??

사람은
- 경험과 감에 의존한다
- 피드백이 느리다 (결국 유지보수의 어려움으로 이어진다)
=> 이 모든 단점들이 결국 소프트웨어의 신뢰도를 낮추게 되는 결점이 된다 (버그가 무한생성)
테스트 코드의 장점은?
- 자동화가 가능하다
- 피드백이 빠르다
=> 결국 신뢰도가 높아지게 한다
자동화 테스트? 수동 테스트랑 뭐가 다른디요?
- 수동 테스트는 -> 사람이 사람의 눈으로 검증
- 자동화 테스트는 -> 기계로 검증

=> 검증 주체의 차이
단위 테스트 (Unit test)
- 작은 코드 단위를 독립적으로 검증하는 테스트
- 작은 코드 단위: class, method 등..
- 독립적: 외부 상황에 의존하지 않음
- 검증 속도가 빠르고 안정적이다
=> 로직 코드가 변경되더라도, 테스트 코드를 통해 정상적으로 동작하는지 (사람의 개입 없이) 체크할 수 있다
단위 테스트 도구
- Junit
- 단위 테스트 프레임 워크
- AssertJ
- 테스트 라이브러리
- api 및 메소드 체이닝(가독성)을 지원하므로 깔끔하게 코드를 작성할 수 있다
package sopt.study.testcode.beverage;
import org.junit.jupiter.api.Test;
import sopt.study.testcode.hyeeum.cafekiosk.unit.beverage.Americano;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
class AmericanoTest {
@Test // 테스트임을 명시
void getName() {
Americano americano = new Americano();
assertEquals("아메리카노", americano.getName()); // jUnit 사용 -> 두 값이 같은지 검증
assertThat(americano.getName()).isEqualTo("아메리카노"); // AssertJ 사용 -> 동작 동일,but 가독성이 좋아서 AssertJ 사용 권장함
}
@Test
void getPrice() {
Americano americano = new Americano();
assertThat(americano.getPrice()).isEqualTo(4000);
}
}
어떻게 테스트 케이스를 세분화할 수 있을까?
테스트 케이스의 종류

- 해피 케이스
- 요구사항에 드러나 있는 경우
- 정상적인 입력 조건

요구사항이 안명확해서 새드한 우
- 예외 케이스 (저희끼리 새드 케이스라고 부를까요 ㅋㅋ)
- 요구사항에 드러나 있지 않은 경우 (상식적으로 바로 생각은 안나는데 충분히 일어날 수 있는 일들,,)
- 비정상적인 입력 조건
경계값 테스트
- 범위, 구간, 날짜 등등 경계값이 있는 경우 -> 경계값에서 동작이 달라질 수 있음 -> 경계값 검증은 필수적
해피케이스 -> num 3에 대한 케이스
예외케이스 -> 2에 대한 케이스 (일반적으로 경계 바로 아래 영역을 테스트함)
=> 경계값이 있다면 그 경계값에서 테스트할 수 있도록 고민해볼 것!
어떤 코드가 테스트 하기 쉬운가?

순수 함수 (같은 값을 넣으면 항상 같은 값이 나옴)
어떤 코드가 테스트 하기 어려운가?
- 관측할 때마다 값이 다른 코드 (외부세계에 의존)
- 현재 날짜
- 현재 시간
- 랜덤 값
- 전역 변수
- 전역 함수
- 사용자 입력 등등
- 외부 세계에 영향을 주는 코드
- 표준 출력(로그)
- 메세지 발송 (이메일 전송)
- 데이터베이스 (파일) 기록 등등
테스트하기 어려운 코드가 추가되었다면?
결국 전체를 테스트하기 어려워진다
그러므로 외부로 분리하는 것이 중요!
예시) 가게의 운영시간일때에만 주문 가능한 키오스크가 필요해요!
- Local Date Time을 method 내부에 위치시킨다면? Hmmm,,,, 우리가 원하는 값으로 테스트 불가!
- 우리가 검증해야하는 것은 LocalDateTime.now()가 아님을 주의하자

현재 시각이 중요한게 X, 어떤 시각이 주어졌을 때 조건을 판단하는 것이 중요
그러므로 class 내부에 넣는 것이 아닌, parameter를 통해 DateTime을 가져오는 것이 좋다
// 수정 전 : LocalDateTime을 class 내부에 생성함 -> 실행마다 달라지는 문제 발생!
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(LocalDateTime.now(), beverages);
}
// 수정 후 : LocalDateTime을 받아옴
public Order createOrder(LocalDateTime localDateTime) {
LocalTime currentTime = localDateTime.toLocalTime();
if (currentTime.isBefore(SHOP_OPEN_TIME) || currentTime.isAfter(SHOP_CLOSE_TIME)) {
throw new IllegalArgumentException("주문 시간이 아닙니다. 관리자에게 문의바람 ㅋㅋ");
}
return new Order(LocalDateTime.now(), beverages);
}
// 우리가 원하는 값에 대한 명확한 테스트가 가능하도록 변경!
Order order = cafeKiosk.createOrder(LocalDateTime.of(2025, 1, 17, 10, 0));
=> LocalDateTime.now()처럼 실행마다 값이 변하거나 외부 시스템에 의존하는 코드는 테스트 결과가 불안정하므로
이런 코드를 분리하면 예측 가능한 테스트가 가능해진다
=> 이와 같이 분리할수록 테스트 가능한 코드는 많아진다!