용어집 · 테스트 기초
단위 테스트
영어: Unit test
다른 이름: unit testing, 유닛 테스트, 단위 테스트 코드
정의
함수나 클래스 같은 작은 코드 단위를 데이터베이스, 네트워크, 다른 서비스와 격리한 상태에서 검사하는 자동화 테스트입니다.
단위 테스트는 보통 함수, 메서드, 클래스 같은 작은 코드 단위 하나를 실행해 주어진 입력에 대한 출력이나 동작을 확인합니다. 실제 데이터베이스, HTTP 호출, 파일 시스템 없이 메모리 안에서 실행되므로 수 밀리초면 끝나고, 실패한다면 정확히 하나의 이유로 실패합니다. 단위 테스트는 테스트 피라미드의 맨 아래층을 이루며, 대개 개발자가 코드와 함께 작성합니다.
예시 (JUnit 5, Java 21)
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
class DiscountCalculatorTest {
private final DiscountCalculator calculator = new DiscountCalculator();
@ParameterizedTest
@CsvSource({ "99.99, 0.00", "100.00, 10.00", "250.00, 25.00" })
void appliesTenPercentFromOneHundred(double total, double expected) {
assertEquals(expected, calculator.discountFor(total), 0.001);
}
@Test
void rejectsNegativeTotals() {
assertThrows(IllegalArgumentException.class, () -> calculator.discountFor(-1));
}
}
파라미터화 테스트에는 junit-jupiter-params 아티팩트가 필요하며, 이는 junit-jupiter 통합 의존성에 포함되어 있습니다. (실제 금액을 다루는 코드라면 double 대신 BigDecimal을 써야 하지만, 여기서는 단순하게 두었습니다.)
좋은 단위 테스트의 조건
- 빠르고 결정적(deterministic)입니다. sleep, 시계, 난수, 네트워크를 쓰지 않습니다.
LocalDate.now()를 호출하는 대신 고정된Clock을 주입하십시오. - 격리되어 있습니다. 테스트를 느리게 하거나 예측 불가능하게 만드는 의존성은 테스트 더블로 대체합니다.
- 테스트 하나에 동작 하나를 검증하고, 그 동작을 이름에 드러내어 실패만 봐도 원인을 알 수 있게 합니다.
- 구현이 아니라 동작을 테스트합니다. private 메서드가 아니라 출력과 관찰 가능한 효과에 대해 어설션해야 리팩터링해도 테스트가 깨지지 않습니다.
단위 테스트와 통합 테스트의 차이
단위 테스트는 나머지를 모두 대체한 상태에서 여러분의 로직을 확인합니다. 통합 테스트는 실제 컴포넌트들이 함께 동작하는지, 예컨대 리포지토리가 실제 데이터베이스와, 클라이언트가 실제(또는 컨테이너화된) 서비스와 제대로 맞물리는지 확인합니다. 단위 테스트는 로직 오류를 적은 비용으로 잡아내고, 통합 테스트는 단위 테스트에서 mock으로 가려지는 연결, 설정, 직렬화, 쿼리 오류를 잡아냅니다. 단위 테스트만으로 이루어진 스위트는 애플리케이션이 시작조차 못 하는 상황에서도 전부 초록색일 수 있습니다.
“단위”를 정의하는 방법
정답은 없습니다. 어떤 팀은 클래스 하나를 단위로 보고 협력 객체를 모두 mock 처리합니다. 다른 팀은 하나의 동작을 구현하는 작은 클래스 묶음을 단위로 보고, 프로세스 경계를 넘는 것만 대체합니다. 두 번째 방식이 리팩터링에 덜 취약한 테스트를 만드는 경향이 있습니다.