[JUnit5] 기본 사용법 정리

1. 시작하며

JUnit5는 가장 인기 있는 단위 테스트 프레임 워크이다. JUnit5 의 기본 기능에 대해 알아보자

2. JUnit5 란?

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

  • JUnit Platform : 테스트를 실행해주는 런처 제공, TestEngine API 정의
  • JUnit Jupiter : TestEngine API 구현체로 JUnit5 제공
  • JUnit Vintage : JUnit4와 JUnit43을 지원하는 TestEngine 구현체

2.1 JUnit Platform

  • JVM에서 테스트 프레임워크를 시작하기 위한 런처를 제공한다.
  • Junit과 클라이언트 간의 인터페이스를 제공한다.
  • JUnit 플랫폼에서 실행되는 테스트 프레임워크를 개발하기 위한 TestEngine API를 정의한다.

2.2 Junit Jupiter

  • 테스트 작성을 위한 새로운 프로그래밍 모델과 확장 모델 조합이다.
  • Junit 4 에서 다음 기능이 추가되었다.

@TestFactory – 동적 테스트 지원
@DisplayName – 테스트 클래스, 테스트 메서드에 대해 사용자 정의하는 이름 노출
@Nested – 클래스가 중첩된 비정적 테스트 클래스임을 나타냄
@Tag – 테스트를 필터링하기 위한 태그 선언
@ExtendWith – custom extensions 등록
@BeforeEach – 각각의 테스트 메서드 실행 이전에 매번 실행 (JUnit4: @Before)
@AfterEach – 각각 테스트 메소드 실행 이후에 실행 (JUnit4: @After)
@BeforeAll – 현재 클래스의 모든 테스트 메서드 실행 이전에 한 번만 실행 (JUnit4: @BeforeClass)
@AfterAll –현재 클래스의 모든 테스트 메소드 실행 이후에 한번만 실행 ( (JUnit4: @AfterClass)
@Disable – 테스트 클래스, 테스트 메서드 비활성화 (JUnit4: @Ignore)

2.3 JUnit Vintage

  • Junit 5에서 Junit 4와 Junit3가 동작할 수 있도록 지원한다.

3. JUnit5 시작하기

  • Java 8 이상에서 동작한다.

maven dependency 추가

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.8.2</version>
    <scope>test</scope>
</dependency>

4. 주요 Annotations

@BeforeAll / @BeforeEach / @AfterEach / @AfterAll

  • @BeforeEach: 각각의 테스트 메서드 실행 이전에 매번 실행 (JUnit4: @Before)
  • @AfterEach: 각각 테스트 메소드 실행 이후에 실행 (JUnit4: @After)
  • @BeforeAll: 현재 클래스의 모든 테스트 메서드 실행 이전에 한 번만 실행 (JUnit4: @BeforeClass)
  • @AfterAll: 현재 클래스의 모든 테스트 메소드 실행 이후에 한번만 실행 ( (JUnit4: @AfterClass)

@BeforeAll, @AfterAll 은 static으로 선언되어야 한다.

import static org.junit.jupiter.api.Assumptions.assumeTrue;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class SampleSuccessTests {

    @BeforeAll
    static void setup() {
        System.out.println("@BeforeAll - executes once before all test methods in this class");
    }

    @BeforeEach
    void init() {
        System.out.println("@BeforeEach - executes before each test method in this class");
    }

    @AfterEach
    void tearDown() {
        System.out.println("@AfterEach - executed after each test method.");
    }

    @AfterAll
    static void tearDownAll() {
        System.out.println("@AfterAll - executed after all test methods.");
    }

    @Test
    void successTest1() {
        System.out.println("executes successTest1");
        assumeTrue("abc".contains("a"));
    }

    @Test
    void successTest2() {
        System.out.println("executes successTest2");
        assumeTrue("abc".contains("b"));
    }
}

위 코드를 실행해보면 다음과 같다.

@DisplayName

  • Test Class 나 Test Method에 사용자 정의 이름 지정 가능하다.
  • 공백, 특수문자, 이모지 등 입력 가능하며 test report 나 IDE에서 확인 가능하다.
@DisplayName("성공 테스트")
public class SampleSuccessTests {

    @DisplayName("a가 포함되어 있음 🌱")
    @Test
    void successTest1() {
        System.out.println("executes successTest1");
        assumeTrue("abc".contains("a"));
    }

    @DisplayName("b 가 포함되어있음 ╯°□°)╯")
    @Test
    void successTest2() {
        System.out.println("executes successTest2");
        assumeTrue("abc".contains("b"));
    }
}

@Disabled

  • 테스트를 비활성화시켜서 테스트를 skip 할 수 있도록 한다.
  • JUnit4의 '@Ignore'와 동일 기능
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

@Disabled("Disabled until bug #99 has been fixed")
class DisabledClassDemo {

    @Test
    void testWillBeSkipped() {
    }

}

아래와 같이 테스트가 skip 된다.

환경에 따라서 test를 Skip 하도록 설정 가능하다.

@Test
@EnabledIfEnvironmentVariable(named = "ENV", matches = "staging-server")
void onlyOnStagingServer() {
    // ...
}

@Test
@DisabledIfEnvironmentVariable(named = "ENV", matches = ".*development.*")
void notOnDeveloperWorkstation() {
    // ...
}

5. Assertions

  • JUnit Jupiter는 JUnit 4에서 제공하는 Assertions 기능과 더불어 Java 8의 lambda를 지원하는 Assertions 이 추가되었다.
  • JUnit Jupiter assertions 은 org.junit.jupiter.api.- Assertionsstatic로 이동되었다
@Test
void lambdaExpressions() {
    List numbers = Arrays.asList(1, 2, 3);
    assertTrue(numbers.stream()
      .mapToInt(i -> i)
      .sum() > 5, () -> "Sum should be greater than 5");
}
  • assertAll() 을 사용하여 Assertion 을 그룹화하여 실행 가능하다.
@Test
void groupAssertions() {
    int[] numbers = {0, 1, 2, 3, 4};
    assertAll("numbers",
        () -> assertEquals(numbers[0], 1),
        () -> assertEquals(numbers[3], 3),
        () -> assertEquals(numbers[4], 1)
    );
}

6. Assumptions

  • Assumptions 은 특정 조건이 충족될 때만 테스트를 실행한다.
  • 즉 특정 조건을 만족하지 않으면 테스트를 더 이상 진행하지 않는다.
  • 일반적으로 테스트가 제대로 실행하기 위한 필요한 외부조건에 사용된다. (ex. Dev 환경에서만 테스트)
  • JUnit Jupiter assumptions 은 org.junit.jupiter.api.Assumptionsstatic로 이동되었다
  • assumeTrue(), assumeFalse(), assumingThat()
@Test
void testOnlyOnDevServer() {
    // DEV 환경 에서만 test 실행
    assumeTrue("DEV".equals(System.getenv("ENV")));
    assertEquals(1, "a".length(), "is always equal");
}

@Test
void testOnlyOnPrdServer() {
    // PRD 환경이 아닐 때만 test 실행
    assumeFalse("PRD".equals(System.getenv("ENV")));
    assertEquals(4, "real".length(), "is always equal");
}

@Test
void testInAllEnvironments() {
    assumingThat("PRD".equals(System.getenv("ENV")),
        () -> {assertEquals(1, "a".length(), "is always equal");
    });

    assertEquals(3, "all".length(), "is always equal");
}

7. Exception Testing

  • 예외를 테스트하는 방법은 두 가지가 있다.
  • assertThrows 사용하기
  • assertEquals 로 에러 메시지 비교하기
@Test
void assertThrowsException() {
    String str = null;
    assertThrows(IllegalArgumentException.class, () -> {
        Integer.valueOf(str);
    });
}

@Test
void shouldThrowException() {
    Exception exception = assertThrows(ArithmeticException.class, () -> {
        int divide = 1/0;
    });
    assertEquals("/ by zero", exception.getMessage());
}

8. Test Suites

  • 여러 테스트 클래스를 모아서 테스트할 수 있다.
  • @SelectPackages, @SelectClasses
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.SuiteDisplayName;
import org.junit.runner.RunWith;

@RunWith(org.junit.platform.runner.JUnitPlatform.class)
@SuiteDisplayName("JUnit Platform Suite Demo")
@SelectPackages("example")
public class JUnitPlatformSuiteDemo {
}
@RunWith(JUnitPlatform.class)
@SelectClasses({AssertionTest.class, AssumptionTest.class, ExceptionTest.class})
public class AllUnitTest {

}

9. Dynamic Tests

  • 런타임에 생성된 테스트 케이스를 선언하고 실행할 수 있는 JUnit 5의 동적 테스트 기능
  • 런타임에서 테스트 케이스를 동적으로 정의할 수 있습니다.
@TestFactory
public Stream<DynamicTest> translateDynamicTestsFromStream() {
    return in.stream()
      .map(word ->
          DynamicTest.dynamicTest("Test translate " + word, () -> {
            int id = in.indexOf(word);
            assertEquals(out.get(id), translate(word));
          })
    );
}

10. Parameter를 사용하여 반복 테스트 하기

  • @ParameterizedTest 로 파라미터에 따라 반복적인 테스트 수행
  • @ValueSource로 테스트에 사용할 변수 지정
@ParameterizedTest
@DisplayName("Should create shapes with different numbers of sides")
@ValueSource(ints = {3, 4, 5, 8, 14})
void shouldCreateShapesWithDifferentNumbersOfSides(int expectedNumberOfSides) {
    Shape shape = new Shape(expectedNumberOfSides);
    assertEquals(expectedNumberOfSides, shape.numberOfSides());
}
@ParameterizedTest(name = "{0}")
@DisplayName("Should not create shapes with invalid numbers of sides")
@ValueSource(ints = {0, 1, 2, Integer.MAX_VALUE})
void shouldNotCreateShapesWithInvalidNumbersOfSides(int expectedNumberOfSides) {
    assertThrows(IllegalArgumentException.class,
        () -> new Shape(expectedNumberOfSides));
}

11. 중첩된 계층구조 테스트 하기

  • 중첩된 계층 구조를 가진 테스트 메서드 작성 가능
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

public class NestedTests {
    @Nested
    @DisplayName("결제를 시도 할 때")
    class whenTryPay {

        @Nested
        @DisplayName("카드로 시도 할 때")
        class payWithCard {
            @Test
            @DisplayName("신용카드 결제가 성공한다")
            void successPayWithCreditCard() {

            }
            @Test
            @DisplayName("체크카드 결제가 성공한다")
            void successPayWithDebitCard() {

            }
        }
        @Nested
        @DisplayName("현금으로 시도할 때")
        class payWithCash {
            @Test
            @DisplayName("현금 결제 성공한다")
            void successPayWithCash() {

            }
        }
    }
}

10. 마치며

JUnit5에 추가된 주요 기능을 확인해보았다.

참고

반응형

댓글

Designed by JB FACTORY