나를 기록하다
article thumbnail
반응형

Junit5


우아한테크코스의 프리코스를 진행하며 테스트의 중요성을 계속해서 느끼고 있다. 프리코스를 경험하기 전까지 테스트 코드에 전혀 무지했기에 자바 진영에서 가장 많이 사용되는 Junit, 그리고 그중에서 최신 버전인 Junit5에 대해서 정리하고자 한다.

 

JUnit 5란 무엇인가?

공식 홈페이지를 살펴보면 아래와 같이 설명한다.

What is JUnit5

해석하자면 아래와 같다.

Junit5는 세 가지 하위 프로젝트의 여러 모듈로 구성되어 있다.

Junit 5 = Junit Platform + Junit Jupiter + Junit Vintage

 

Junit Platform

  • JVM에서 테스트 프레임워크를 실행하기 위한 기반 역할
  • 플랫폼에서 실행되는 테스트 프레임워크를 개발하기 위한 TestEngine API를 정의
  • 콘솔 런처를 제공하여 플랫폼을 명령 줄에서 실행
  • 사용자 지정 테스트 스위트를 실행하기 위한 Junit Platform Suite Engine을 제공

Junit Jupiter

  • Junit 5에서 테스트와 extension을 작성하기 위한 프로그래밍 모델과 extension 모델의 조합
  • Jupiter 하위 프로젝트는 플랫폼에서 Jupiter 기반 테스트를 실행하기 위한 TestEngine을 제공

Junit Vintage

  • 플랫폼엥서 Junit 3, Junit 4 기반 테스트를 실행하기 위한 TestEngine을 제공
  • 이를 사용하려면 클래스 경로나 모듈 경로에 Junit 4.12 이상의 버전이 존재해야 한다.

 

사용 방법

Junit 5 공식 문서에서 제공하는 예시는 아래와 같다.

 

첫 번째 테스트 케이스

import static org.junit.jupiter.api.Assertions.assertEquals;

import example.util.Calculator;

import org.junit.jupiter.api.Test;

class MyFirstJUnitJupiterTests {

    private final Calculator calculator = new Calculator();

    @Test
    void addition() {
        assertEquals(2, calculator.add(1, 1));
    }

}

addition 테스트를 진행하여 assertEquals를 통해 calculator.add(1,1)의 값이 2와 일치하는지 확인한다.

 

어노테이션

어노테이션 설명

@Test


메서드가 테스트 메서드임을 나타낸다.
JUnit 4의 @Test 어노테이션과 달리 이 어노테이션은 어트리뷰트를 선언하지 않는데, 이는 JUnit Jupiter의 테스트 확장이 자체 전용 어노테이션을 기반으로 작동하기 때문이다. 이러한 메서드는 재정의하지 않는 한 상속된다.


@ParameterizedTest


메서드가 매개변수화된 테스트임을 나타낸다.
이러한 메서드는 재정의 되지 않는 한 상속된다.


@RepeatedTest


메서드가 반복 테스트에 대한 테스트 템플릿임을 나타낸다.
이러한 메서드는 재정의 되지 않는 한 상속된다.


@TestFactory


메서드가 동적 테스트를 위한 테스트 팩토리임을 나타낸다.
이러한 메서드는 재정의 되지 않는 한 상속된다.


@TestTemplate


메서드가 등록된 provider가 반환한 호출 컨텍스트의 수에 따라 여러 번 호출되도록 설계한 테스트 케이스용 템플릿임을 나타낸다.
이러한 메서드는 재정의 되지 않는 한 상속된다.

@TestClassOrder


어노테이션된 테스트 클래스에서 @Nested(중첩된) 테스트 클래스에 대한 테스트 클래스 실행 순서를 구성하는 데 사용된다.
이러한 어노테이션은 상속된다.


@TestMethodOrder


어노테이션된 테스트 클래스의 테스트 메서드 실행 순서를 구성하는 데 사용되며 Junit 4의 @FixMethodOrder과 유사하다.
이러한 어노테이션은 상속된다.


@TestInstance


어노테이션된 테스트 클래스에 대한 테스트 인스턴스 라이프사이클을 구성하는데 사용된다.
이러한 어노테이션은 상속된다.


@DisplayName


테스트 클래스나 테스트 메서드의 사용자 지정 표시 이름을 선언한다.
이러한 어노테이션은 상속되지 않는다.


@DisplayNameGeneration


테스트 클래스에 대한 사용자 지정 표시 이름 생성기를 선언한다.
이러한 어노테이션은 상속된다.


@BeforeEach


이 어노테이션이 달린 메서드가 현재 클래스의 각 @Test, @RepeatedTest, @ParameterizedTest 또는 @TestFactory 메서드 전에 실행되어야 함을 나타낸다(Junit 4의 @Before과 유사).
이러한 메서드는 재정의되거나 대체되지 않는 한 상속된다(Java의 표시 규칙에 관계없이 시그니처를 기준으로 대체된다).


@AfterEach


이 어노테이션이 달린 메서드가 현재 클래스의 각 @Test, @RepeatedTest, @ParameterizedTest 또는 @TestFactory 메서드 이후에 실행되어야 함을 나타낸다(Junit 4의 @After와 유사).
이러한 메서드는 재정의되거나 대체되지 않는 한 상속된다(Java의 표시 규칙에 관계없이 시그니처를 기준으로 대체된다).


@BeforeAll


이 어노테이션이 달린 메서드가 현재 클래스의 모든 @Test, @RepeatedTest, @ParameterizedTest 및 @TestFactory 메서드보다 먼저 실행되어야 함을 나타낸다(Junit 4의 @BeforeClass과 유사).
이러한 메서드는 숨겨지거나 재정의되거나 대체되지 않는 한(Java의 표시 규칙에 관계없이 시그니처를 기준으로 대체되지 않는 한) 상속되며, "클래스별 테스트" 인스턴스 수명 주기가 사용되지 않는 한 정적이어야 한다.


@AfterAll


이 어노테이션이 달린 메서드가 현재 클래스의 모든 @Test, @RepeatedTest, @ParameterizedTest 및 @TestFactory 메서드 이후에 실행되어야 함을 나타낸다(Junit 4의 @AfterClass과 유사).
이러한 메서드는 숨겨지거나 재정의되거나 대체되지 않는 한(Java의 표시 규칙에 관계없이 시그니처를 기준으로 대체되지 않는 한) 상속되며, "클래스별" 테스트 인스턴스 수명 주기가 사용되지 않는 한 정적이어야 한다.


@Nested


이 어노테이션이 달린 클래스가 정적이 아닌 중첩된 테스트 클래스임을 나타낸다.
Java 8 ~ Java 15까지에서는 "클래스별" 테스트 인스턴스 라이프사이클을 사용하지 않는 한 @BeforeAll 및 @AfterAll 메서드를 @Nested 테스트 클래스에서 직접 사용할 수 없다.
Java 16부터는 테스트 인스턴스 라이프사이클 모드 중 하나를 사용하여 @Nested 테스트 클래스에서 @BeforeAll 및 @AfterAll 메서드를 정적으로 선언할 수 있다.
이러한 어노테이션은 상속되지 않는다.


@Tag


클래스 또는 메서드 수준에서 테스트를 필터링하기 위한 태그를 선언하는데 사용되며, TestNG의 테스트 그룹 또는 Junit 4의 카테고리와 유사하다.
이러한 어노테이션은 클래스 수준에서는 상속되지만 메서드 수준에서는 상속되지 않는다.


@Disabled


테스트 클래스 또는 테스트 메서드를 비활성화하는 데 사용되며 Junit 4의 @Ignore와 유사하다.
이러한 어노테이션은 상속되지 않는다.


@Timeout


테스트, 테스트 팩토리, 테스트 템플릿 또는 라이프사이클의 메서드의 실행이 지정된 기간을 초과하는 경우 실패하는 데 사용된다.
이러한 어노테이션은 상속된다.


@ExtendWith


선언으로 extension을 등록하는 데 사용된다.
이러한 어노테이션은 상속된다.


@RegisterExtension


필드를 통해 프로그래밍 방식으로 extension을 등록하는 데 사용된다.
이러한 필드는 shadowed 처리되지 않는 한 상속된다.
여기서 shadowed란, 부모 클래스에서 상속된 확장 필드를 자식 클래스에서 동일한 필드로 덮어쓰는 것을 의미한다. 자식 클래스에서 부모 클래스의 확장 필드를 동일한 이름으로 다시 정의하면, 자식 클래스의 필드가 부모 클래스의 필드를 "가리는(shadow)" 것이다.


@TempDir


라이프사이클 메서드 또는 테스트 메서드에서 필드 주입 또는 매개변수 주입을 통해 임시 디렉토리를 제공하는 데 사용된다.
org.junit.jupiter.api.io 패키지 내에 존재한다.

 

메타 어노테이션과 합성 어노테이션

메타 어노테이션
다른 어노테이션을 설명하거나 수정하는 어노테이션. 어노테이션에 대한 어노테이션이라고 생각할 수 있다.
자바에서는 어노테이션을 정의할 때 `@interface` 키워드를 사용한다. 이러한 사용자 정의 어노테이션은 다른 코드 요소에 대한 정보를 제공하거나 컴파일러나 프레임워크에 추가 정보를 제공하는 데 사용된다.
[예시] `@Deprecated` 메타 어노테이션은 해당 요소가 더 이상 권장되지 않음을 나타낸다.
합성 어노테이션
합성 어노테이션은 하나 이상의 다른 어노테이션을 결합하여 새로운 어노테이션을 만드는 것을 의미한다.
즉 어노테이션을 조합하여 더 복잡한 어노테이션을 만드는 것이다.
합성 어노테이션을 통해 코드를 더 명확하게 만들 수 있고, 중복을 줄일 수 있으며, 특정 기능 또는 관례를 정의하는 데 사용된다.
Java에서는 다른 어노테이션을 `@` 기호를 사용하여 결합하거나 중첩하여 합성 주석을 만들 수 있다.
[예시] `@BeforeEach`와 `@AfterEach`와 같은 테스트 라이프사이클 관련 어노테이션을 조합하여 `@Test` 메서드 앞뒤에 실행되는 설정을 정의한 `@BeforeEach`와 `@AfterEach`로 이루어진 합성 어노테이션을 만들 수 있다.

Junit Jupiter 어노테이션은 메타 어노테이션처럼 사용할 수 있다. 이미 정의된 어노테이션을 가져와서 이를 조합하여 새로운 어노테이션을 만들 수 있으며, 이 새로운 어노테이션은 그 안에 포함된 메타 어노테이션의 의미와 동작을 자동으로 상속받는다.

예를 들어, "fast"을 나타내는 `@Tag("fast")` 어노테이션을 여러 곳에서 사용하는 대신, `@Fast`라는 새로운 어노테이션을 만들 수 있다. `@Fast` 어노테이션 내에 `@Tag("fast")`를 포함시키면 `@Fast` 어노테이션을 사용할 때 자동으로 `@Tag("fast")`의 의미와 동작을 상속받게 된다. 이렇게 하면 코드를 간결하게 만들고, 중복을 줄이며, 코드를 더 읽기 쉽게 만들 수 있다.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.junit.jupiter.api.Tag;

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
public @interface Fast {
}

그리고 `@FastTest`라는 커스텀 어노테이션을 도입할 수 있다. 이 어노테이션은 `@Tag("Fast")`와 `@Test`를 대체하여 사용할 수 있는 어노테이션이다. 간단히 말하면 `@FastTest` 어노테이션을 사용하면 자동으로 `@Tag("Fast")`와 `@Test`의 의미와 동작을 상속받게 되어 훨씬 간단하게 작성할 수 있고 중복을 줄일 수 있어 코드의 가독성도 향상된다. 

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
@Test
public @interface FastTest {
}

Junit 5는 아래 코드를 "fast" 태그가 지정된 `@Test` 메서드로 자동 인식한다.

@FastTest
void myFastTest() {
    // ...
}

 

 

정의

Platform Concepts
- 컨테이너란?
    테스트 트리에서 다른 컨테이너 또는 테스트를 자식으로 포함하는 노드. 일반적으로 테스트 클래스를 나타낸다.
- 테스트란?
    실행될 때 예상 동작을 검증하는 테스트 트리에서의 노드. 주로 @Test 메서드로 나타낸다.
Jupiter Concepts
- 라이프사이클 메서드란?
    @BeforeEach 또는 @AfterEach로 직접 주석이 달리거나 메타 어노테이션으로 처리된 메서드를 의미한다.
    이러한 메서드는 테스트 수명주기에 관련한 작업을 수행한다.
- 테스트 클래스란?
    하나 이상의 테스트 메서드를 포함하는 모든 최상위 클래스, 정적 멤버 클래스 또는 @Nested 클래스이다.
    테스트 클래스는 추상 클래스일 수 없으며 단일 생성자를 가져야 한다.
- 테스트 메서드란?
    @Test, @RepeatedTest, @ParameterizedTest, @TestFactory 또는 @TestTemplate와 같은
    어노테이션이 직접적으로 또는 메타 어노테이션으로 사용된 모든 인스턴스 메서드를 나타낸다.
    다만 @Test를 제외한 이러한 어노테이션들은 테스트 트리에서 테스트를 그룹화하는 컨테이너를 만들거나
    (특히 @TestFactory의 경우) 다른 컨테이너를 만들 수 있다. 이렇게 함으로써 테스트가 조직되고 실행된다.

 

 

테스트 클래스와 메서드

테스트 메서드와 라이프사이클 메서드는 현재의 테스트 클래스 내에서 지역적으로 선언될 수 있고, 부모 클래스에서 상속될 수 있으며, 인터페이스에서 상속받을 수 있다. 또한 테스트 메서드와 라이프사이클 메서드는 추상 메서드일 수 없으며, 갑승ㄹ 반환해서는 안된다(값을 반환해야 하는 @TestFactory 메서드를 제외하고).

클래스와 메서드의 가시성
테스트 클래스. 테스트 메서드, 그리고 라이프사이클 메서드는 반드시 public일 필요는 없지만, private일 수는 없다.
일반적으로 테스트 클래스. 테스트 메서드, 라이프사이클 메서드에서 public 수식어를 생략하는 것을 권장한다.
단 다른 패키지의 테스트 클래스에서 확장되는 경우와 같이 기술적인 이유가 있을 때(예: 다른 패키지의 테스트 클래스에서 확장하는 경우) public으로 선언하는 것이 좋다. 또한, 모듈 경로를 사용할 때 Java 모듈 시스템을 간소화하기 위해 클래스와 메서드를 public으로 만드는 경우도 있다.

아래 테스트 클래스는 @Test 메서드 및 지원되는 모든 라이프사이클 메서드의 사용을 보여준다.

 

표준 테스트 클래스

import static org.junit.jupiter.api.Assertions.fail;
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.Test;

class StandardTests {

    @BeforeAll
    static void initAll() {
    }

    @BeforeEach
    void init() {
    }

    @Test
    void succeedingTest() {
    }

    @Test
    void failingTest() {
        fail("a failing test");
    }

    @Test
    @Disabled("for demonstration purposes")
    void skippedTest() {
        // not executed
    }

    @Test
    void abortedTest() {
        assumeTrue("abc".contains("Z"));
        fail("test should have been aborted");
    }

    @AfterEach
    void tearDown() {
    }

    @AfterAll
    static void tearDownAll() {
    }

}

오늘은 Junit 5가 무엇인지와 어노테이션의 종류와 역할, 테스트 클래스와 메서드에 대해서 알아보았다.

다음에는 Assertions와 Assumptions 등 공식 문서를 바탕으로 한 내용을 포스팅할 예정이고 이번 포스팅은 여기서 마무리하겠다.

반응형
profile

나를 기록하다

@prao

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

profile on loading

Loading...