테스트코드/작성방법

1. Basic Unit Test

feel2 2024. 3. 26. 07:35
반응형

기본적인 단위 테스트에 대해 알아본다. 

 

1.1. 단위 테스트

 

작은 코드 단위(클래스 or 메서드)를 독립적으로 검증하는 테스트이다. 검증 속도가 빠르고, 안정적인 장점이 있다.

 

1.2. 준비 및 환경설정

Project 설정


jdk 11
spring-boot 2.7.17
dependency:
spring-boot-starter-data-jpa,
spring-boot-starter-web,
spring-boot-starter-test,
com.h2database(버전주의),
lombok
-group: msa
-artifact: tc
JUnit5(단위 테스트를 위한 테스트 프레임워크)
AssertJ(테스트 코드 작성을 원활하게 돕는 테스트 라이브러리)
H2 Database: 로컬에 h2 데이터베이스를 다운 받아 실행한다.(참고: https://yjkim-dev.tistory.com/3 )
-IDE: IntelliJ

 

1.2. 단위 테스트 예제

 

1. 단위 테스트 작성 - 1 단위 테스트 예제 (순수 JAVA 테스트)

 

순수 JAVA 코드로 [카페 키오스크]를 간략하게 구현하여 단위 테스트를 살펴본다.

(sample 디렉토리를 추가하여, ~/sample/* 디렉토리에 [카페 키오스크]를 위한 클래스 구성)
Beverage.java: 음료 인터페이스, 가격과 명칭 확인 기능
Americano.java, Latte.java : 음료 인터페이스 구현체
Order.java: 주문시간과 음료리스트를 가지는 객체
AmericanoTest.java: Americano.java 의 기본적인 단위 테스트 작성

 

package msa.tc.sample.beverage;

public interface Beverage {

    String getName();

    int getPrice();
}

 

 

package msa.tc.sample.beverage;

public class Americano implements Beverage {
    @Override
    public String getName() {
        return "아메리카노";
    }

    @Override
    public int getPrice() {
        return 4000;
    }
}

 

 

package msa.tc.sample.beverage;

public class Latte implements Beverage {
    @Override
    public String getName() {
        return "라떼";
    }

    @Override
    public int getPrice() {
        return 4500;
    }
}

 

package msa.tc.sample.order;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import simple.testcode.sample.beverage.Beverage;

import java.time.LocalDateTime;
import java.util.List;

@Getter
@RequiredArgsConstructor
public class Order {

    private final LocalDateTime orderDateTime;
    private final List<Beverage> beverages;
}

 

package msa.tc.sample.beverage;

import org.junit.jupiter.api.Test;

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(), "아메리카노");

        // junit 의 assert* 보단 AssertJ 라이브러리를 사용한다.

        // assertj
        assertThat(americano.getName()).isEqualTo("아메리카노");
    }

    @Test
    void getPrice() {
        Americano americano = new Americano();

        assertThat(americano.getPrice()).isEqualTo(4000);
    }
}

 

테스트 클래스 생성: product code 에서 테스트 하고자 하는 대상 Americano 클래스에서 cmd + shift + T 를 누르면 create new Test 기능을 통해서 AmericanoTest 클래스를 생성할 수 있다.

 

Create Test

 

 

테스트 방법: 단위 테스트를 진행하고자 하는 메소드를 작성하고 메소드명 위에 jupiter의 Test 어노테이션을 달자. 인텔리제이에서 테스트 실행 아이콘으로 제공한다. 필요에 따라 getName 과 같은 메소드별로 테스트를 진행하거나 AmericanoTest로 테스트 실행하여 여러 테스트를 동시에 실행할 수 있다.

 

AmericanoTest로 다중 테스트(getName(), getPrice()) 진행한 화면

 

 

대표적인 형식: assertThat(테스트 하고자 하는 대상).isEqualTo(비교값)



assert 함수: 테스트 코드 작성시 assertThat, assertThatThrownBy 과 같은 assert* 함수를 주로 사용한다. assert 함수는 jupiter와 assertj 라이브러리가 제공한다. assertj 에서 제공하는 함수를 사용해서 단위 테스트를 진행한다.



(참고: 혹시 테스트가 위의 스크린샷처럼 JUnit 이아닌 gradle 로 테스트가 실행될 수 있다. 그럴 땐, IDE 설정을 변경해 주면 된다. https://m.blog.naver.com/varkiry05/221806279836 )

 

1. 단위 테스트 작성 - 2 단위 테스트 예제 (순수 JAVA 테스트) (테스트명(DisplayName)을 명확하게 문장으로 표현하자.)

package msa.tc.sample;

import lombok.Getter;
import simple.testcode.sample.beverage.Beverage;

import java.util.ArrayList;
import java.util.List;

@Getter
public class CafeKiosk {

    private final List<Beverage> beverages = new ArrayList<>();


    public void add(Beverage beverage) {
        beverages.add(beverage);
    }

    /**
     * 음료 추가 (한 종류의 음료 여러 잔을 한 번에 담는 기능)
     */
    public void add(Beverage beverage, int count) {
        if (count <= 0) {
            throw new IllegalArgumentException("음료는 1잔 이상 주문 할 수 있음.");
        }

        for (int i = 0; i < count; i++) {
            beverages.add(beverage);
        }
    }

    // 음료 한 잔 제거
    public void remove(Beverage beverage) {
        beverages.remove(beverage);
    }

    // 음료 전체 제거
    public void clear() {
        beverages.clear();
    }
}

 

package msa.tc.sample;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import simple.testcode.sample.beverage.Americano;
import simple.testcode.sample.beverage.Latte;

import static org.assertj.core.api.Assertions.assertThat;

class CafeKioskTest {

    private CafeKiosk cafeKiosk;


    // [명확한 문장으로 표현] "음료 1개 추가 테스트" 라는 테스트명 보단
    // 도메인 정책, 용어를 사용한 명확한 문장을 추천함
    @DisplayName("음료 1잔 추가하면 주문 목록에 담긴다.")
    @Test
    void add() {
        // given
        cafeKiosk = new CafeKiosk();
        Americano americano = new Americano();


        // when
        cafeKiosk.add(americano);


        // then
        assertThat(cafeKiosk.getBeverages().size()).isEqualTo(1);
        assertThat(cafeKiosk.getBeverages()).hasSize(1);

        assertThat(cafeKiosk.getBeverages().get(0).getName()).isEqualTo("아메리카노");
    }


    @DisplayName("추가한 음료 1 잔을 취소한다.")
    @Test
    void remove() {
        // given
        cafeKiosk = new CafeKiosk();
        Americano americano = new Americano();
        cafeKiosk.add(americano, 1);

        assertThat(cafeKiosk.getBeverages().size()).isEqualTo(1);


        // when
        cafeKiosk.remove(americano);


        // then
        assertThat(cafeKiosk.getBeverages().size()).isEqualTo(0);
    }

    @DisplayName("추가한 음료 전체를 제거한다.")
    @Test
    void clear() {
        // given
        cafeKiosk = new CafeKiosk();
        Americano americano = new Americano();
        Latte latte = new Latte();

        cafeKiosk.add(americano);
        cafeKiosk.add(latte);

        assertThat(cafeKiosk.getBeverages().size()).isEqualTo(2);


        // when
        cafeKiosk.clear();


        // then
        assertThat(cafeKiosk.getBeverages().size()).isEqualTo(0);
        assertThat(cafeKiosk.getBeverages()).isEmpty();
    }
}

 

단위 테스트 예제 후 화면
 

테스트 케이스의 세분화와 테스트명 작성 방법에 대해 알아본다.

1. 단위 테스트 작성 - 3 단위 테스트 작성 가이드(테스트 케이스 세분화) (해피케이스와 예외케이스를 고려. 특히 경계값은 주의가 필요함)

 


테스트 case 세분화: 프로덕션의 기능을 테스트 할 때, 여러 경우의 수를 고민하여 작성해보자. 대표적으로 해피 케이스와 예외(edge) 케이스(경계값) 테스트가 있다.
다양한 테스트 케이스를 통해 프로덕션 코드를 자연스럽게 설명할 수 있고, 이해하는 시각을 넓힐 수 있다.

 

    // [해피케이스]
    @DisplayName("음료를 여러 잔 추가할 수 있다.")
    @Test
    void addSeveralBeverages() {
        // given
        cafeKiosk = new CafeKiosk();
        Americano americano = new Americano();
        Latte latte = new Latte();


        // when
        cafeKiosk.add(americano, 2);
        cafeKiosk.add(latte, 1);


        // then
        assertThat(cafeKiosk.getBeverages().get(0)).isEqualTo(americano);
        assertThat(cafeKiosk.getBeverages().get(1)).isEqualTo(americano);
        assertThat(cafeKiosk.getBeverages().get(2)).isEqualTo(latte);
    }

    // [예외케이스][경게값]
    @DisplayName("[예외 case] 음료를 1잔 미만으로 추가하면 예외가 발생한다.")
    @Test
    void addZeroBeverages() {
        // given
        cafeKiosk = new CafeKiosk();
        Americano americano = new Americano();


        // when && // then (경계값 테스트)
        Assertions.assertThatThrownBy(() -> cafeKiosk.add(americano, 0))
                .isInstanceOf(IllegalArgumentException.class);

        Assertions.assertThatThrownBy(() -> cafeKiosk.add(americano, 0))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessage("음료는 1잔 이상 주문 할 수 있음.");
    }

 

테스트명은 문장으로 작성: 테스트 코드의 테스트명을 명사의 나열이 아닌, 프로덕션의 기능을 설명하는 문장으로 작성하자.

 

음료 1개 추가 테스트 → 음료를 1개 추가할 수 있다. → 음료를 1개 추가하면 주문 목록에 담긴다.

특정 시간 이전에 주문을 생성하면 실패한다. → 영업 시작 시간 이전에는 주문을 생성할 수 없다.

 

테스트 행위에 대한 결과까지 기술하자.
'특정 시간' 보단 ‘카페의 영업 시간’ 처럼 도메인에서 사용하는 전문적인 용어를 사용하여 표현하자. 의미전달에 도움이 될 것이다.
이렇게 명확하게 작성한다면 테스트 코드를 보고자 하는 또는 프로덕션 코드를 파악하고자 하는 다른 팀원들에게도 자연스럽게 기능에 대해 구체적으로 의미 전달이 가능할 것이다.

 


잘 작성한 테스트 코드 팀의 자산으로 공유 해보자.

반응형

'테스트코드 > 작성방법' 카테고리의 다른 글

6. Presentation Layer Test  (1) 2024.03.26
5. Business Layer Test  (1) 2024.03.26
4. 외부 시스템과의 연계 테스트  (1) 2024.03.26
3. Persistence Layer Test  (0) 2024.03.26
2. Layered Architecture  (0) 2024.03.26