자바의 JUnit에 대해 설명하는 글을 작성하겠습니다. JUnit은 자바에서 단위 테스트(Unit Test)를 작성하고 실행하기 위한 가장 널리 사용되는 프레임워크입니다. 특히 JUnit 5는 모듈화된 구조와 다양한 기능을 제공하여 테스트 작성의 유연성과 가독성을 높여줍니다. JUnit은 어노테이션을 통해 테스트의 생명주기와 동작을 제어하며, 이를 통해 개발자는 테스트 코드를 간결하고 명확하게 작성할 수 있습니다.

아래에서는 JUnit 5의 주요 어노테이션을 표로 정리한 뒤, 각 어노테이션에 대한 설명과 예시를 상세히 다루겠습니다.

---

### JUnit 5 주요 어노테이션 표

| 어노테이션                  | 설명                                                                                   |
|----------------------------|----------------------------------------------------------------------------------------|
| `@Test`                   | 해당 메서드가 테스트 메서드임을 나타냄                                                  |
| `@BeforeAll`              | 모든 테스트 메서드 실행 전에 한 번만 실행되는 초기화 메서드 (static 필요)               |
| `@AfterAll`               | 모든 테스트 메서드 실행 후에 한 번만 실행되는 정리 메서드 (static 필요)                 |
| `@BeforeEach`             | 각 테스트 메서드 실행 전에 실행되는 초기화 메서드                                       |
| `@AfterEach`              | 각 테스트 메서드 실행 후에 실행되는 정리 메서드                                         |
| `@Disabled`               | 테스트 클래스나 메서드를 비활성화하여 실행하지 않음                                     |
| `@DisplayName`            | 테스트 클래스나 메서드에 사용자 정의 이름을 지정                                        |
| `@ParameterizedTest`      | 동일한 테스트를 여러 입력값으로 반복 실행                                              |
| `@ValueSource`            | `@ParameterizedTest`와 함께 사용되며, 단일 값의 배열을 제공                            |
| `@CsvSource`              | `@ParameterizedTest`와 함께 사용되며, CSV 형식의 데이터를 제공                         |
| `@MethodSource`           | `@ParameterizedTest`와 함께 사용되며, 메서드에서 제공되는 데이터를 사용                 |
| `@RepeatedTest`           | 테스트를 지정된 횟수만큼 반복 실행                                                     |
| `@Tag`                    | 테스트에 태그를 지정하여 필터링 가능                                                    |
| `@Nested`                 | 테스트 클래스 내에 중첩된 테스트 클래스를 정의                                         |
| `@Timeout`                | 테스트 실행 시간이 지정된 시간을 초과하면 실패 처리                                     |
| `@ExtendWith`             | 사용자 정의 확장을 등록하여 테스트 동작을 커스터마이징                                  |

---

### 각 어노테이션 상세 설명 및 예시

#### 프로젝트 설정
JUnit 5를 사용하려면 `pom.xml`에 다음 의존성을 추가해야 합니다:
```xml
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.10.2</version>
    <scope>test</scope>
</dependency>
```

#### 1. `@Test`
- **설명**: 단위 테스트 메서드를 정의합니다. 별도의 설정 없이 실행됩니다.
- **예시**:
```java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class CalculatorTest {
    @Test
    void testAddition() {
        Calculator calc = new Calculator();
        assertEquals(4, calc.add(2, 2), "2 + 2는 4여야 합니다.");
    }
}

class Calculator {
    int add(int a, int b) { return a + b; }
}
```

#### 2. `@BeforeAll`
- **설명**: 모든 테스트 전에 한 번만 실행되며, static 메서드여야 합니다.
- **예시**:
```java
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

public class DatabaseTest {
    @BeforeAll
    static void initDatabase() {
        System.out.println("데이터베이스 초기화");
    }

    @Test
    void testQuery() {
        System.out.println("쿼리 테스트");
    }
}
// 출력: "데이터베이스 초기화" -> "쿼리 테스트"
```

#### 3. `@AfterAll`
- **설명**: 모든 테스트 후에 한 번만 실행되며, static 메서드여야 합니다.
- **예시**:
```java
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;

public class DatabaseTest {
    @AfterAll
    static void closeDatabase() {
        System.out.println("데이터베이스 종료");
    }

    @Test
    void testQuery() {
        System.out.println("쿼리 테스트");
    }
}
// 출력: "쿼리 테스트" -> "데이터베이스 종료"
```

#### 4. `@BeforeEach`
- **설명**: 각 테스트 메서드 실행 전에 실행됩니다.
- **예시**:
```java
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class UserTest {
    private User user;

    @BeforeEach
    void setUp() {
        user = new User("홍길동");
        System.out.println("유저 설정");
    }

    @Test
    void testName() {
        System.out.println("이름 테스트: " + user.getName());
    }
}
// 출력: "유저 설정" -> "이름 테스트: 홍길동"
```

#### 5. `@AfterEach`
- **설명**: 각 테스트 메서드 실행 후에 실행됩니다.
- **예시**:
```java
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

public class UserTest {
    private User user;

    @AfterEach
    void tearDown() {
        user = null;
        System.out.println("유저 정리");
    }

    @Test
    void testName() {
        user = new User("김영희");
        System.out.println("이름 테스트");
    }
}
// 출력: "이름 테스트" -> "유저 정리"
```

#### 6. `@Disabled`
- **설명**: 테스트를 비활성화하여 실행되지 않게 합니다.
- **예시**:
```java
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

public class FeatureTest {
    @Disabled("아직 구현되지 않음")
    @Test
    void testFeature() {
        System.out.println("이 테스트는 실행되지 않습니다.");
    }
}
// 출력: 없음
```

#### 7. `@DisplayName`
- **설명**: 테스트에 사용자 친화적인 이름을 붙입니다.
- **예시**:
```java
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class MathTest {
    @Test
    @DisplayName("덧셈 테스트")
    void testAdd() {
        assertEquals(5, 2 + 3);
    }
}
// 테스트 결과에 "덧셈 테스트"로 표시됨
```

#### 8. `@ParameterizedTest`
- **설명**: 동일한 테스트를 여러 입력값으로 실행합니다.
- **예시** (with `@ValueSource`):
```java
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class NumberTest {
    @ParameterizedTest
    @ValueSource(ints = {2, 4, 6})
    void testEvenNumbers(int number) {
        assertTrue(number % 2 == 0, number + "는 짝수여야 합니다.");
    }
}
// 2, 4, 6 각각에 대해 테스트 실행
```

#### 9. `@CsvSource`
- **설명**: CSV 형식의 데이터를 제공하여 여러 파라미터로 테스트합니다.
- **예시**:
```java
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class MathTest {
    @ParameterizedTest
    @CsvSource({"1, 2, 3", "4, 5, 9"})
    void testAddition(int a, int b, int expected) {
        assertEquals(expected, a + b);
    }
}
// (1, 2, 3), (4, 5, 9)로 테스트 실행
```

#### 10. `@MethodSource`
- **설명**: 사용자 정의 메서드에서 테스트 데이터를 제공합니다.
- **예시**:
```java
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.stream.Stream;

public class StringTest {
    @ParameterizedTest
    @MethodSource("provideStrings")
    void testLength(String input, int expected) {
        assertEquals(expected, input.length());
    }

    private static Stream<Arguments> provideStrings() {
        return Stream.of(
            Arguments.of("hello", 5),
            Arguments.of("world", 5)
        );
    }
}
```

#### 11. `@RepeatedTest`
- **설명**: 테스트를 지정된 횟수만큼 반복 실행합니다.
- **예시**:
```java
import org.junit.jupiter.api.RepeatedTest;

public class RandomTest {
    @RepeatedTest(3)
    void testRandom() {
        System.out.println("랜덤 테스트: " + Math.random());
    }
}
// 출력: 3번 실행, 각각 다른 랜덤 값
```

#### 12. `@Tag`
- **설명**: 테스트에 태그를 지정하여 특정 테스트만 실행할 수 있습니다.
- **예시**:
```java
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

public class TaggedTest {
    @Test
    @Tag("fast")
    void testFast() {
        System.out.println("빠른 테스트");
    }

    @Test
    @Tag("slow")
    void testSlow() {
        System.out.println("느린 테스트");
    }
}
// IDE나 빌드 도구에서 "fast" 태그만 실행 가능
```

#### 13. `@Nested`
- **설명**: 테스트 클래스 내에 중첩된 테스트를 정의하여 계층적 테스트를 구성합니다.
- **예시**:
```java
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

public class OuterTest {
    @Test
    void outerTest() {
        System.out.println("외부 테스트");
    }

    @Nested
    class InnerTest {
        @Test
        void innerTest() {
            System.out.println("내부 테스트");
        }
    }
}
```

#### 14. `@Timeout`
- **설명**: 테스트가 지정된 시간을 초과하면 실패 처리합니다.
- **예시**:
```java
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;

import java.util.concurrent.TimeUnit;

public class TimeoutTest {
    @Test
    @Timeout(value = 1, unit = TimeUnit.SECONDS)
    void testTimeout() throws InterruptedException {
        Thread.sleep(2000); // 2초 대기 -> 실패
    }
}
```

#### 15. `@ExtendWith`
- **설명**: 사용자 정의 확장을 등록하여 테스트 동작을 확장합니다.
- **예시** (간단한 확장 예시):
```java
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;

@ExtendWith(CustomExtension.class)
public class ExtensionTest {
    @Test
    void testWithExtension() {
        System.out.println("확장 테스트");
    }
}

class CustomExtension implements TestExecutionExceptionHandler {
    @Override
    public void handleTestExecutionException(ExtensionContext context, Throwable throwable) {
        System.out.println("예외 처리: " + throwable.getMessage());
    }
}
```

---

### 결론
JUnit 5는 다양한 어노테이션을 통해 테스트의 생명주기 관리, 반복 실행, 데이터 기반 테스트, 태깅 등 강력한 기능을 제공합니다. 이를 활용하면 코드 품질을 높이고, 유지보수성을 개선할 수 있습니다. 특히 `@ParameterizedTest`와 `@Nested` 같은 기능은 복잡한 테스트 시나리오를 체계적으로 관리하는 데 유용합니다. 프로젝트에서 JUnit을 사용할 때는 팀원 모두가 어노테이션의 역할과 사용법을 이해하고, 지속적인 통합(CI) 환경에서 테스트를 자동화하는 것이 좋습니다.

추가 질문이 있다면 언제든 물어보세요!