JUnit5 기초

java-logo-1.png


1. JUnit / JUnit5

 JUnit은 자바에서 널리 쓰이는 단위 테스트 프레임워크로, 단위 테스트를 쉽게 작성하고 관리할 수 있게 해줍니다. JUnit 3.x에서 JUnit의 기초적인 구조가 완성되고, 이후 JUnit5까지 발전하였습니다.

 

junittst.drawio.png
JUnit5

 

 

JUnit 5는 JUnit의 최신 버전으로, JUnit Platform, JUnit Jupiter, JUnit Vintage로 구성된 모듈형 아키텍처로 설계되었습니다.

  • JUnit Platform: JUnit 5의 핵심 플랫폼으로, 테스트를 실행하고 결과를 리포팅하는 역할을 합니다.
  • JUnit Jupiter: JUnit 5에서 새로 추가된 테스트 프로그래밍 모델로, 새로운 어노테이션과 확장성을 제공합니다.
  • JUnit Vintage: JUnit 3.x와 4.x 버전의 테스트를 JUnit 5 플랫폼에서 실행할 수 있도록 지원하는 모듈입니다.

 

 

2. JUnit5 테스트 생명주기

 

2.1. 인스턴스 관리: @TestInstance

 JUnit 5는 각 테스트 메서드를 실행할 때 독립적인 테스트 인스턴스를 사용합니다. 즉, 테스트 클래스 내의 각각의 @Test 메서드가 실행될 때마다 새로운 인스턴스가 생성됩니다. 이를 "per-method" 인스턴스 생성이라고 합니다. 이 방식은 테스트 간의 의존성을 제거하고, 각각의 테스트가 독립적으로 수행될 수 있도록 보장합니다.

 

기본적으로는 위에서 설명한 대로 "per-method" 방식이지만, JUnit 5에서는 @TestInstance 어노테이션을 통해 인스턴스 관리 전략을 변경할 수 있습니다.

 

  • @TestInstance(Lifecycle.PER_METHOD): 기본값으로, 각 테스트 메서드마다 새로운 인스턴스가 생성됩니다. 이를 통해 테스트 간 상태가 공유되지 않으므로 테스트의 독립성이 보장됩니다.
  • @TestInstance(Lifecycle.PER_CLASS): 테스트 클래스당 하나의 인스턴스만 생성되며, 모든 테스트 메서드가 동일한 인스턴스를 공유하게 됩니다. 이를 통해 인스턴스 상태를 테스트 메서드 간에 공유할 수 있습니다. 예를 들어, 테스트 간 공유해야 할 데이터가 있거나 비용이 많이 드는 설정 작업을 피하고 싶을 때 유용할 수 있습니다.
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class ATest {
...
}

 

2.2. 테스트 인스턴스 생명주기 흐름

  1. 테스트 클래스가 로드됩니다.
  2. 각 테스트 메서드를 실행할 때마다 새로운 테스트 클래스 인스턴스가 생성됩니다.
  3. @BeforeAll, @BeforeEach 메서드가 먼저 실행되고, 그다음 @Test 메서드가 실행됩니다.
  4. 테스트 메서드가 종료되면, @AfterAll, @AfterEach 메서드가 실행됩니다.
  5. 테스트 클래스의 인스턴스는 테스트 메서드가 종료되면 더 이상 사용되지 않고 가비지 컬렉터에 의해 회수됩니다.

 

 

3. JUnit5의 어노테이션

 

3.1. @Test, @DisplayName

- @Test

  • JUnit에서 테스트 메서드를 정의하는 가장 기본적인 어노테이션입니다.
  • 이 어노테이션이 붙은 메서드는 테스트 대상이며, JUnit에 의해 자동으로 실행됩니다.
  • @Test를 사용하여 테스트 케이스를 작성하고, 각 메서드는 독립적으로 실행됩니다.
@Test 
void myTest() { 
// 테스트 코드 
}

 

- @DisplayName

  • 테스트 메서드나 테스트 클래스의 이름을 보다 가독성 있게 지정하는 데 사용하는 어노테이션입니다.
  • 기본적으로 테스트 메서드 이름이 그대로 출력되지만, @DisplayName을 사용하면 테스트 이름을 설명적으로 바꿔서 가독성을 높일 수 있습니다.
@Test 
@DisplayName("Addition of two numbers") 
void testAddition() { 
// 테스트 코드 
}

 

3.2. @BeforeAll, @AfterAll

- @BeforeAll

  • 테스트 클래스 내에서 한 번만 실행되는 메서드를 지정하는 어노테이션입니다.
  • 테스트 클래스 내에서 가장 먼저 실행되며, 주로 공유 리소스의 초기화나 설정에 사용됩니다.
  • 메서드는 static으로 선언되어야 합니다(기본 생명주기 @TestInstance(Lifecycle.PER_METHOD)인 경우).
@BeforeAll 
static void init() { 
// 테스트 전체에 필요한 초기 설정 
}
 

- @AfterAll

  • 테스트 클래스 내에서 한 번만 실행되는 메서드를 지정하는 어노테이션입니다.
  • 모든 테스트 메서드가 실행된 후에 마지막에 실행되며, 리소스 해제나 정리 작업을 수행하는 데 사용됩니다.
  • @BeforeAll과 마찬가지로 static으로 선언되어야 합니다(기본 생명주기 @TestInstance(Lifecycle.PER_METHOD)인 경우).
@AfterAll 
static void cleanup() { 
// 테스트 완료 후 자원 해제 등 정리 작업 
}

 

3.3. @BeforeEach, @AfterEach

- @BeforeEach

  • 테스트 메서드가 실행되기 직전에 호출되는 메서드를 지정하는 어노테이션입니다.
  • 모든 테스트 메서드마다 테스트 전 준비 작업을 수행합니다.
  • 예를 들어, 객체 초기화나 설정 작업이 필요할 때 사용합니다.
@BeforeEach 
void setUp() { 
// 각 테스트 전에 실행되는 설정 작업 
}
 

- @AfterEach

  • 각 테스트 메서드가 실행된 직후에 호출되는 메서드를 지정하는 어노테이션입니다.
  • 각 테스트 메서드가 끝난 후 정리 작업을 수행합니다.
@AfterEach 
void tearDown() { 
// 각 테스트 후에 실행되는 정리 작업 
}

 

3.4. @repeatedTest

  • 동일한 테스트 메서드를 여러 번 반복 실행할 수 있게 해주는 어노테이션입니다. 
  • 반복 횟수 정보 활용: RepeatedTest 메서드의 파라미터로 RepeatedTest.RepetitionInfo를 사용하면 반복 횟수 및 현재 반복 인덱스에 접근할 수 있습니다.
@RepeatedTest(3)
void repeatedTestWithRepetitionInfo(RepetitionInfo repetitionInfo) {
    System.out.println("Repetition " + repetitionInfo.getCurrentRepetition() + " of " + repetitionInfo.getTotalRepetitions());
}

 

 

3.5. @Disabled

  • 특정 테스트 메서드나 테스트 클래스의 실행을 비활성화하는 어노테이션입니다. 이를 통해 일시적으로 테스트를 건너뛰고, 추후 다시 활성화할 수 있습니다. 주로 개발 중 테스트가 실패하거나 현재 실행할 필요가 없는 경우에 사용됩니다.
@Disabled
@Test
void skippedTest() {
    // 이 테스트는 실행되지 않습니다.
}

 

3.6. @nested

  • 테스트 클래스를 중첩할 수 있게 해주는 어노테이션입니다.
  • 테스트를 그룹화하고, 특정 조건이나 상황에 따라 테스트를 구성할 수 있습니다. 
  • 중첩 클래스는 상위 클래스의 필드와 메서드에 접근할 수 있어, 공통 설정을 재사용할 수 있습니다. 이를 통해 코드 중복을 줄일 수 있습니다.
  • 보통 테스트 코드의 가독성 개선과 구조화 목적으로 사용됩니다.
class CalculatorTest {

    @Nested
    class WhenAdding {
        @Test
        void SumForTwoPositiveNumbers() {
            // 두 양수 덧셈 테스트
        }

        @Test
        void SumForPositiveAndNegativeNumber() {
            // 양수와 음수 덧셈 테스트
        }
    }

    @Nested
    class WhenSubtracting {
        @Test
        void DifferenceForTwoPositiveNumbers() {
            // 두 양수 뺄셈 테스트
        }
        
        @Test
        void DifferenceForPositiveAndNegativeNumber() {
            // 양수와 음수 뺄셈 테스트
        }   
    }
}

 

3.7. @ParameterizedTest

  • 파라미터화된 테스트를 작성할 수 있게 해주는 어노테이션입니다.
  • 동일한 테스트 메서드를 다양한 입력 값과 기대 결과를 가지고 테스트할 수 있어, 코드의 다양한 경로를 검증할 수 있습니다.
  • 입력 값을 제공하는 여러 가지 방법(예: @EmptySource, @ValueSource, @CsvSource, @MethodSource 등)을 지원합니다.

 

- @EmptySource: 비어 있는 값을 테스트할 때

@ParameterizedTest
@EmptySource
void isBlank_ShouldReturnTrueForEmptyStrings(String input) {
    assertTrue(Strings.isBlank(input));
}

 

- @ValueSource: 단순한 값을 사용할 때

class MathUtilsTest {

    @ParameterizedTest
    @ValueSource(ints = {1, 2, 3, 4, 5})
    void testIsPositive(int number) {
        assertTrue(number > 0, "Number should be positive");
    }
}

 

- @CsvSource: 여러 개의 값과 기대 결과를 사용할 때

class CalculatorTest {

    @ParameterizedTest
    @CsvSource({
        "1, 1, 2",
        "2, 3, 5",
        "4, 5, 9"
    })
    void testAddition(int a, int b, int expectedSum) {
        assertEquals(expectedSum, a + b);
    }
}

 

- @MethodSource: 외부 메서드를 통해 입력 값을 제공할 때

class StringUtilsTest {

    @ParameterizedTest
    @MethodSource("provideStringsForConcat")
    void testConcat(String first, String second, String expected) {
        assertEquals(expected, first + second);
    }

    static Stream<Arguments> provideStringsForConcat() {
        return Stream.of(
            Arguments.of("Hello", "World", "HelloWorld"),
            Arguments.of("JUnit", "5", "JUnit5")
        );
    }
}

 

3.8. Assert 항목들

- assertEquals

  • 두 개의 값이 동일한지를 검증하는 메서드입니다. 주로 테스트의 예상 결과와 실제 결과를 비교할 때 사용됩니다.
@Test
void testAddition() {
    int expected = 5;
    int actual = 2 + 3;
    assertEquals(expected, actual, "2 + 3 should equal 5");
}

 

- assertThrows

  • 예상하는 예외를 던지는지를 검증하는 메서드입니다. 예외가 발생하지 않으면 테스트는 실패합니다.
@Test
void testDivisionByZero() {
    assertThrows(ArithmeticException.class, () -> {
        int result = 1 / 0; // 0으로 나누기
    });
}

 

- assertTimeout

  • 주어진 시간 내에 완료되는지를 검증하는 메서드입니다. 코드 블록이 지정한 시간 내에 실행되지 않으면 테스트는 실패합니다.
@Test
void testTimeout() {
    assertTimeout(Duration.ofMillis(100), () -> {
        // 실행 시간 제한이 있는 코드
        Thread.sleep(50); // 50ms 대기
    });
}

 

- assertAll

  • 여러 개의 assert를 그룹화하여 모두 검증할 수 있게 해주는 메서드입니다. 하나의 assert가 실패하더라도 나머지 assert는 영향을 받지 않고 계속 진행됩니다.
@Test
void testMultipleAssertions() {
    int result = 5;

    assertAll("result checks",
        () -> assertEquals(5, result),
        () -> assertTrue(result > 0),
        () -> assertNotNull(result)
    );
}

 


https://junit.org/junit5/docs/current/user-guide/

https://www.baeldung.com/parameterized-tests-junit-5

반응형