[박우빈, Practical Testing #2]단위테스트

dev_lee·2024년 11월 10일
post-thumbnail

수동 테스트 vs 자동화된 테스트

수동 테스트

@Test
    void add_menual_test() {
        CafeKiosk cafeKiosk = new CafeKiosk();
        cafeKiosk.add(new Americano());

        System.out.println(">>> 담긴 음료 수 : " + cafeKiosk.getBeverages().size());
        System.out.println(">>> 담긴 음료 : " + cafeKiosk.getBeverages().get(0).getName());
    }

이 테스트 코드의 문제점
1. 결과를 콘솔에 찍어서 확인하므로 결국 테스트 최종 단계에서 사람이 개입해야 한다.
2. 다른 사람이 이 테스트 코드를 봤을 때 테스트에 대한 명확한 목적을 알 수 없고 테스트의 성공, 실패 여부를 알 수 없다.

다음 단락에서 자동화 테스트에 대해 알아본다.


JUnit5로 테스트하기

단위 테스트란?

  • 작은 코드 단위(클래스 or 메서드)를 독립적으로 검증하는 테스트
    - 외부 환경에 의존하지 않고 하나의 클래스나 메서드 단위로 검증
  • 검증 속도가 빠르고, 안정적이다.

JUnit5란?

단위 테스트를 위한 테스트 프레임워크

AssertJ

  • 테스트 코드 작성을 원활하게 돕는 테스트 라이브러리
  • 풍부한 API, 메서드 체이닝 지원

JUnit5와 AssertJ 이용하여 테스트 코드 작성
프로젝트 build.gradle에 아래 의존성 패키지를 추가해서 사용할 수 있다.

testImplementation 'org.springframework.boot:spring-boot-starter-test'

자동화 테스트

  • 음료를 추가하는 테스트
@Test
    void add() {
        CafeKiosk cafeKiosk = new CafeKiosk();
        cafeKiosk.add(new Americano());

        assertThat(cafeKiosk.getBeverages()).hasSize(1);
        assertThat(cafeKiosk.getBeverages().get(0).getName()).isEqualTo("아메리카노");
    }
  • 음료를 취소하는 테스트
@Test
    void remove() {
        CafeKiosk cafeKiosk = new CafeKiosk();
        Americano americano = new Americano();

        cafeKiosk.add(americano);
        assertThat(cafeKiosk.getBeverages()).hasSize(1);

        cafeKiosk.remove(americano);
        assertThat(cafeKiosk.getBeverages()).isEmpty();
    }

단위 테스트가 많이 작성된다면 프로덕션 로직이 변경 되더라도 지속적인 테스트 코드 수행으로 프로덕션 코드가 정상적으로 동작하고 있는지 체크할 수 있다.


테스트 케이스 세분화 하기

암묵적이거나 아직 드러지나 않은 요구 사항이 있는가? 라는 질문을 항상 던져야한다.

해피 케이스

해피 케이스는 사용자가 예상한 대로 시스템이 정확하게 동작하는 이상적인 상황을 테스트한다. 시스템이나 함수가 정상 입력을 받아서 기대한 출력 결과를 내놓는지 확인하는 것이 목적이다.

예외 케이스

예외 케이스는 시스템이 비정상적이거나 예외적인 상황에서 어떻게 반응하는지를 테스트한다. 정상 입력 이외의 데이터나 상황으로 인한 오류 처리를 검증하는 것이 목적이다.

해피 케이스와 예외 케이스를 검증할 때는 경계값 테스트가 중요하다.
경계값 : 범위(이상, 이하, 초과, 미만), 구간, 날짜 등

예를 들어, "음료는 최소 3잔 이상부터 주문 가능하다"라는 조건이 있을 때, 해피 케이스는 경계값이 3이므로 3잔 주문에 대해 검증하고, 예외 케이스는 2잔 주문에 대한 검증을 한다.


테스트하기 어려운 영역 분리하기

추가된 요구 사항 : 가게 운영 시간(10:00 ~ 22:00) 외에는 주문을 생성할 수 없다.

  • 테스트 어려운 영역 분리 전
    - 현재 시간이 하드 코딩되어 테스트를 수행하는 시각이 테스트 결과에 영향을 줌.
public class CafeKiosk {

	private static final LocalTime SHOP_OPEN_TIME = LocalTime.of(10, 0);
    private 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);
    }
}
  • 테스트 어려운 영역 분리 후
    - 외부에서 파라미터로 받도록 LocalDateTime 분리했다.
public class CafeKiosk {

	private static final LocalTime SHOP_OPEN_TIME = LocalTime.of(10, 0);
    private 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);
    }
}

테스트할 때는 원하는 시각을 파라미터로 넘기면 된다. 이렇게 변경하면 해피 케이스, 예외 케이스 테스트도 파라미터만 변경하여 경계값 테스트가 가능하다.

@Test
    void createOrderOutsideOpenTime() {
        CafeKiosk cafeKiosk = new CafeKiosk();
        Americano americano = new Americano();
        cafeKiosk.add(americano);

        assertThatThrownBy(() -> cafeKiosk.createOrder(LocalDateTime.of(2024, 11, 6, 9, 59)))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessage("주문 시간이 아닙니다. 관리자에게 문의하세요.");
    }

테스트하기 어려운 영역

  • 관측할 때 마다 다른 값에 의존하는 코드
    - 현재 날짜/시간, 랜덤 값, 전역 변수/함수, 사용자 입력 등
  • 외부 세계에 영향을 주는 코드
    - 표준 출력, 메세지 발송, 데이터베이스에 기록하기 등

테스트 하기 쉬운 영역 - 순수 함수(pure functions)

  • 같은 입력에는 항상 같은 결과
  • 외부 세상과 단절된 형태
  • 테스트하기 쉬운 코드

테스트 하기 어려운 외부로 분리할수록 테스트 가능한 코드는 많아진다.


출처 - 박우빈, Practical Testing: 실용적인 테스트 가이드

0개의 댓글