단위 테스트(Unit Testing)
소프트웨어 개발에서 개별 코드 단위(예: 함수, 메서드, 클래스 등)가 예상대로 작동하는지 검증하는 테스트 방법이다. 이를 통해 개발자는 코드가 작은 단위에서부터 올바르게 작동하는지 확인할 수 있으며, 전체적인 품질을 향상시킨다.
단위 테스트의 주요 특징
- 작은 코드 단위 테스트: 함수나 메서드와 같은 작은 코드 조각을 테스트한다.
- 독립적: 단위 테스트는 다른 코드나 테스트에 의존하지 않아야 하며, 서로 간섭 없이 실행할 수 있어야 한다.
- 빠른 실행: 작은 단위의 코드만을 테스트하므로 빠르게 실행된다.
- 자동화 기능: 대부분의 단위 테스트는 자동화 도구를 사용하여 지속적으로 실행이 가능해야 한다.
- 반복 가능: 테스트는 여러 번 반복적으로 실행할 수 있어야 하며, 동일한 입력에 대해 항상 동일한 결과를 반환해야 한다.
이에 대한 장점으로는
- 버그 조기 발견: 코드의 작은 부분에서부터 테스트를 하므로 버그를 조기에 발견할 수 있다.
- 리팩토링 안전성 보장: 코드 리팩토링 후에도 기존 기능이 정상 작동하는지 검증할 수 있다.
- 설계 개선: 단위 테스트를 통해 모듈화와 결합도를 낮춘 코드 설계를 유도할 수 있다.
단위 테스트의 예시(Java와 JUnit을 사용한 단위 테스트)
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CalculatorTest {
@Test
public void testAddition() {
Calculator calculator = new Calculator();
int result = calculator.add(10, 5);
assertEquals(15, result);
}
}
위 코드에서 Calculator 클래스의 add 메서드가 10과 5를 더해 15를 반환하는지 확인하는 단위 테스트이다.
JUnit5
JUnit5는 테스트 프레임워크이며 모듈화된 구조로 설계되어 있어 더 유연하고, 확장 가능하며 다양한 테스트 요구사항을 수용할 수 있다.
JUnit5 구조
JUnit5는 JUnit Platform, JUnit Jupiter, JUnit Vintage라는 세 가지 모듈로 구성된다.
JUnit Platform
- JUnit5의 기본 런타임 환경을 제공한다. 테스트를 발견하고 실행하는 역할을 하며, IDE(예: IntelliJ, Eclipse)나 빌드 도구(예: Maven, Gradle)가 이를 통해 테스트를 실행한다.
- 플랫폼은 여러 테스트 엔진을 지원이 가능하게 설계되어 있어 JUnit뿐 아니라, 다른 테스트 프레임워크(예: Spock,Cucumber)와 통합될 수 있다.
- TestEngine 인터페이스를 구현한 다양한 테스트 엔진이 JUnit Platform에서 동작한다.
JUnit Jupiter
JUnit5의 핵심 모듈로, 새로운 테스트와 API와 프로그래밍 모델을 제공한다. 대부분의 새로운 기능은 Jupiter 모듈에 포함되어 있다.
JUnit5에서 테스트를 작성할 때 사용하는 대부분의 어노테이션과 메서드가 Jupiter에 포함되어 있다.
얘도 TestEngine인터페이스를 구현하여 실제로 테스트를 실행하는 엔진이다.
JUnit Vintage
JUnit5 환경에서 JUnit4나 JUnit3 테스트 코드를 실행할 수 있도록 지원한다.
새로운 Jupiter API로 마이그레이션하지 않고도 기존 테스트를 유지하고 실행할 수 있도록 호환성을 제공한다.
JUnit Jupiter의 주요 어노테이션
@Test:
- 테스트 메서드를 정의하는 기본 애너테이션이다. JUnit4의 @Test와 동일하게 사용되며, 테스트가 성공 또는 실패했는지 확인할 수 있다.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CalculatorTest {
@Test
void testAddition() {
Calculator calculator = new Calculator();
assertEquals(5, calculator.add(2, 3)); // 두 수를 더해 5가 나오는지 확인
}
}
@BeforeEach:
- 각 테스트 메서드가 실행되기 전에 호출된다. 테스트 메서드마다 공통된 사전 작업이 필요할 때 사용된다. JUnit4의 @Before와 동일한 역할을 하지만, 더 직관적인 이름을 사용한다.
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class CalculatorTest {
private Calculator calculator;
@BeforeEach
void setUp() {
calculator = new Calculator(); // 각 테스트 전에 Calculator 객체 초기화
}
@Test
void testAddition() {
assertEquals(5, calculator.add(2, 3)); // 테스트 코드
}
}
@AfterEach:
- 각 테스트 메서드 실행 후에 호출된다. 리소스를 해제하거나 정리 작업을 수행하는 데 사용된다. JUnit4의 @After와 유사한 역할입니다.
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class CalculatorTest {
private Calculator calculator;
@BeforeEach
void setUp() {
calculator = new Calculator(); // 각 테스트 전에 객체 초기화
}
@AfterEach
void tearDown() {
calculator = null; // 각 테스트 후에 객체 정리
}
@Test
void testAddition() {
assertEquals(5, calculator.add(2, 3)); // 테스트 코드
}
}
@BeforeAll:
- 테스트 클래스 내 모든 테스트가 실행되기 전에 한 번만 호출된다. 일반적으로 정적 메서드(static)와 함께 사용된다. 예를 들어, DB 연결을 설정하는 작업을 여기에 포함시킬 수 있다. JUnit4의 @BeforeClass와 유사한 기능이다.
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
class DatabaseTest {
@BeforeAll
static void setUpConnection() {
System.out.println("DB 연결 설정");
// DB 연결 작업 (한 번만 실행)
}
@Test
void testInsert() {
System.out.println("DB Insert 테스트 실행");
// Insert 테스트 실행
}
@Test
void testUpdate() {
System.out.println("DB Update 테스트 실행");
// Update 테스트 실행
}
}
@AfterAll:
- 모든 테스트가 실행된 후 한 번만 호출되며, 리소스 해제 등의 작업을 처리한다. 정적 메서드와 함께 사용된다. JUnit4의 @AfterClass와 동일한 역할이다.
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
class DatabaseTest {
@BeforeAll
static void setUpConnection() {
System.out.println("DB 연결 설정");
}
@AfterAll
static void tearDownConnection() {
System.out.println("DB 연결 해제");
// DB 연결 해제 작업 (한 번만 실행)
}
@Test
void testInsert() {
System.out.println("DB Insert 테스트 실행");
}
@Test
void testUpdate() {
System.out.println("DB Update 테스트 실행");
}
}
@DisplayName:
- 테스트 메서드에 대해 사람이 읽을 수 있는 설명을 추가할 수 있는 어노테이션이다. IDE에서 테스트 이름 대신 해당 설명이 표시되어 가독성을 높인다.
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CalculatorTest {
@Test
@DisplayName("두 숫자를 더하는 기능을 테스트")
void testAddition() {
Calculator calculator = new Calculator();
assertEquals(5, calculator.add(2, 3)); // 설명적인 테스트 이름
}
}
@Disabled:
- 특정 테스트 메서드나 클래스의 테스트를 비활성화할 때 사용된다. 테스트가 현재는 불완전하거나, 구현 중일 때 유용하다. JUnit4의 @Ignore와 동일한 기능이다.
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CalculatorTest {
@Test
@DisplayName("두 숫자를 더하는 기능을 테스트")
void testAddition() {
Calculator calculator = new Calculator();
assertEquals(5, calculator.add(2, 3)); // 설명적인 테스트 이름
}
}
@Disabled: 특정 테스트를 비활성화한다.
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
class CalculatorTest {
@Test
void testAddition() {
Calculator calculator = new Calculator();
assertEquals(5, calculator.add(2, 3)); // 정상 테스트
}
@Test
@Disabled("이 테스트는 버그 #123 해결 후 실행 예정")
void testDivision() {
Calculator calculator = new Calculator();
assertEquals(2, calculator.divide(10, 5)); // 비활성화된 테스트
}
}
@Tag:
- 테스트 그룹을 나눌 수 있는 어노테이션으로, 빌드 도구나 IDE에서 특정 태그를 사용하여 테스트를 필터링할 수 있다. 대규모 프로젝트에서 특정 유형의 테스트(예: 단위 테스트, 통합 테스트)를 구분하고 실행할 때 유용하다.
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
class CalculatorTest {
@Test
@Tag("fast")
void fastTest() {
Calculator calculator = new Calculator();
assertEquals(5, calculator.add(2, 3)); // 빠른 테스트
}
@Test
@Tag("slow")
void slowTest() {
Calculator calculator = new Calculator();
assertEquals(5, calculator.add(2, 3)); // 느린 테스트
}
}
@Nested:
- 중첩 테스트 클래스를 정의할 수 있다. 테스트 클래스 내에서 논리적으로 관련된 테스트를 그룹화할 수 있다. 내부 테스트 클래스에 @BeforeEach와 같은 애너테이션을 독립적으로 사용할 수 있어 복잡한 테스트 구조를 표현할 수 있다.
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CalculatorTest {
private Calculator calculator;
@BeforeEach
void setUp() {
calculator = new Calculator();
}
@Nested
class AdditionTests {
@Test
void testAddPositiveNumbers() {
assertEquals(5, calculator.add(2, 3));
}
@Test
void testAddNegativeNumbers() {
assertEquals(-5, calculator.add(-2, -3));
}
}
@Nested
class SubtractionTests {
@Test
void testSubtractPositiveNumbers() {
assertEquals(1, calculator.subtract(3, 2));
}
@Test
void testSubtractNegativeNumbers() {
assertEquals(-1, calculator.subtract(-3, -2));
}
}
}
JUnit Jupiter 확장 모델
그리고 JUnit5는 확장 가능한 테스트 모델을 제공하는데, 이를 통해 개발자는 기존 어노테이션 외에도 자신만의 확장 기능을 정의하고 사용할 수 있다. 확장 기능을 사용하는 방식은 @ExtendWith 어노테이션을 통해 가능하다.
EX) @ExtendWith 사용
@ExtendWith(TimingExtension.class)
class CalculatorTest {
@Test
void testAddition() {
Calculator calculator = new Calculator();
assertEquals(5, calculator.add(2, 3));
}
}
위 코드는 TimingExtension이라는 확장을 통해 테스트 실행 시간을 기록하는 예다. JUnit5의 확장 모델을 통해 로깅, 의존성 주입, 외부 시스템과의 통합 등을 구현할 수 있다.
조건부 테스트 실행
JUnit5는 조건부로 테스트를 실행할 수 있는 다양한 어노테이션을 제공한다. 예를 들어, 특정 운영체제에서만 테스트를 실행하거나, 특정 JRE 버전에서만 테스트가 동작하도록 할 수 있습니다.
- @EnabledOnOs(OS.WINDOWS): Windows 운영체제에서만 테스트 실행.
- @EnabledIf: 커스텀 조건을 기반으로 테스트 실행을 결정.
Assertions와 Assumptions
JUnit5는 다양한 Assertion과 Assumption 기능을 제공하여 테스트 로직의 정확성을 검증한다.
- Assertions: 테스트 결과를 검증하는 다양한 메서드 제공.
- assertEquals(expected, actual): 두 값이 같은지 검증.
- assertTrue(condition): 조건이 참인지 검증.
- assertThrows(Exception.class, () -> ...): 특정 예외가 발생하는지 검증.
- Assumptions: 테스트가 특정 조건을 만족할 때만 실행되도록 조건을 설정할 수 있다.
- assumeTrue(condition): 조건이 참일 경우에만 테스트 실행.
- assumeFalse(condition): 조건이 거짓일 경우에만 테스트 실행.
JUnit5의 장점 정리
- 모듈화된 구조: JUnit Platform, Jupiter, Vintage로 분리된 모듈 구조를 통해 확장성과 유연성이 크게 향상되었다.
- 확장성: 확장 모델을 통해 커스텀 기능을 쉽게 추가할 수 있으며, @ExtendWith로 다양한 기능을 확장 가능하다.
- 조건부 실행: 특정 조건에 따라 테스트 실행 여부를 제어할 수 있어 더 유연한 테스트 작성이 가능하다.
- 상태 독립성: @Nested 클래스와 각 메서드마다 별도의 설정(@BeforeEach, @AfterEach)을 통해 독립적인 테스트 상태를 유지할 수 있다.
- 테스트 그룹화: @Tag를 통해 테스트 그룹을 쉽게 나누어 관리할 수 있다.
JUnit5는 특히 대규모 프로젝트에서 확장성 있고 체계적인 테스트 환경을 제공하며, 유연한 기능들을 통해 코드 품질을 높이는 데 큰 도움을 준다.
'Spring' 카테고리의 다른 글
Service 테스트 코드, controller 테스트 코드 (0) | 2024.09.13 |
---|---|
AOP(Aspect-Oriented Programming) (0) | 2024.09.12 |
커스텀 어노테이션 (2) | 2024.09.05 |
Edit Configuration을 통해 SQL DB로그인하기 (0) | 2024.09.03 |
N:M(다대다 관계) (0) | 2024.08.30 |