기본적인 단위 테스트에 대해 알아본다.
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 클래스를 생성할 수 있다.
테스트 방법: 단위 테스트를 진행하고자 하는 메소드를 작성하고 메소드명 위에 jupiter의 Test 어노테이션을 달자. 인텔리제이에서 테스트 실행 아이콘으로 제공한다. 필요에 따라 getName 과 같은 메소드별로 테스트를 진행하거나 AmericanoTest로 테스트 실행하여 여러 테스트를 동시에 실행할 수 있다.
대표적인 형식: 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 |