Junit5 - 4. 이론 - 기능 소개

겔로그·2022년 12월 3일
0

Junit5

목록 보기
4/5
post-thumbnail

개요

드디어 Junit5의 기능을 소개하는 시간이 왔습니다!

너무나도 많은 기능을 제공하여 모든 기능을 소개할 경우 오랜 시간이 소요되겠지만, 모든 기능을 알 정도로 테스트 코드를 광적으로 작성하는 개발자가 아닐경우 핵심 기능들에 대해서만 이해하면 좋을 것 같아 핵심 기능이라고 생각 되는 부분들을 공유할 예정입니다.

좀 더 상세한 내용을 알고 싶으실 경우 Junit5 User Guide 혹은 @jaehoonlee의 JUnit-5-공식-가이드-문서-정리를 참고하시는게 좋을 것 같습니다.

Junit5 User Guide에서 공유드릴만한 괜찮은 기능들에 대해 일부 간추렸으며, 해당 부분에 대해 설명하는 시간을 가지겠습니다.

목록

2.8.6. Custom Conditions
2.10. Test Execution Order
2.12. Nested Tests
2.13. Dependency Injection for Constructors and Methods
2.15. Repeated Tests
2.16. Parameterized Tests
2.18. Dynamic Tests
2.19. Timeouts
2.20. Parallel Execution
3.5. Failure Message Arguments

2.8.6 Custom Conditions

개발자는 환경, 조건에 따라 특정 테스트케이스를 @EnabledIf@DisabledIf로 활성화/비활성화 할 수 있습니다.

예시

@Test
@EnabledIf("customCondition")
void enabled() {
  // ...
}
@Test
@DisabledIf("customCondition")
void disabled() {
  // ...
}
boolean customCondition() {
  return true;
}

=> 실행 결과: enabled() 실행, disabled() 비활성화

2.10. Test Execution Order

일반적인 테스트에서는 테스트 순서가 중요하지 않습니다. 하지만, 테스트 순서가 중요한 케이스가 존재할 경우 해당 기능을 이용하여 클래스 내 테스트 메소드 실행 순서를 보장할 수 있습니다.

사용 방법

@TestMethodOrder를 사용하여 원하는 MethodOrderer 구현을 명시합니다.

MethodOrderer.DisplayName: DisplayName 기준으로 정렬 진행
MethodOrderer.MethodName: test method 명칭 기준으로 정렬
MethodOrderer.OrderAnnotation: @Order annotation 기준으로 순서 정렬
MethodOrderer.Random: pseudo-randomly 하게 테스트 실행
MethodOrderer.Alphanumeric: test method 명칭 기준으로 정렬(MethodOrderer.MethodName는 6.0에서 deprecated 예정)

예시

import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
@TestMethodOrder(OrderAnnotation.class)
class OrderedTestsDemo {
 @Test
 @Order(1)
 void nullValues() {
 // perform assertions against null values
 }
 @Test
 @Order(2)
 void emptyValues() {
 // perform assertions against empty values
 }
 @Test
 @Order(3)
 void validValues() {
 // perform assertions against valid values
 }
}

=> OrderAnnotation.class 이용으로 @Order annotation을 이용해 테스트 순서 정렬

2.12. Nested Tests

테스트간 관계되어진 테스트들을 그룹핑하여 관리할 수 있는 방식입니다.

사용 방법

@Nested를 명시할 경우 클래스 내부에 inner class로 test를 그룹핑할 수 있습니다.

예시

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.EmptyStackException;
import java.util.Stack;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

class TestingAStackDemo {
    Stack<Object> stack;

    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew() {
        new Stack<>();
    }

    @Nested
    @DisplayName("when new")
    class WhenNew {
        @BeforeEach
        void createNewStack() {
            stack = new Stack<>();
        }

        @Test
        @DisplayName("is empty")
        void isEmpty() {
            assertTrue(stack.isEmpty());
        }

        @Test
        @DisplayName("throws EmptyStackException when popped")
        void throwsExceptionWhenPopped() {
            assertThrows(EmptyStackException.class, stack::pop);
        }

        @Test
        @DisplayName("throws EmptyStackException when peeked")
        void throwsExceptionWhenPeeked() {
            assertThrows(EmptyStackException.class, stack::peek);
        }
    }

    @Nested
    @DisplayName("after pushing an element")
    class AfterPushing {
        String anElement = "an element";

        @BeforeEach
        void pushAnElement() {
            stack.push(anElement);
        }

        @Test
        @DisplayName("it is no longer empty")
        void isNotEmpty() {
            assertFalse(stack.isEmpty());
        }

        @Test
        @DisplayName("returns the element when popped and is empty")
        void returnElementWhenPopped() {
            assertEquals(anElement, stack.pop());
            assertTrue(stack.isEmpty());
        }

        @Test
        @DisplayName("returns the element when peeked but remains not empty")
        void returnElementWhenPeeked() {
            assertEquals(anElement, stack.peek());
            assertFalse(stack.isEmpty());
        }
    }
}
}

2.13. Dependency Injection for Constructors and Methods

이전의 모든 Junit 버전에서 테스트 생성자나 메소드는 매개변수를 가질 수 없었습니다. Junit5의 Junit Jupiter의 주요 변화 중 하나로서 테스트 생성자와 메서드 모두 매개 변수를 가질 수 있습니다.
따라서, 생성자와 메소드의 의존성 주입이 가능해지면서 유연성을 제공합니다.

자동으로 제공되는 built-in ParameterResolver는 세 종류가 있습니다다.

  • TestInfoParameterResolver
    생성자나 메서드 파라미터가 TestInfo 타입일 경우TestInfoParameterResolverTestInfo 인스턴스를 제공해줍니다.
    TestInfo는 현재 컨테이너 또는 테스트 표시 이름, 테스트 클래스, 테스트 메서드, 태그 등의 정보를 제공합니다.

  • RepetitionInfoParameterResolver
    @RepeatedTest, @BeforeEach, @AfterEach 메서드 파라미터의 타입이 RepetitionInfo라면, RepetitionInfoParameterResolver는 RepetitionInfo(현재 반복 횟수와 총 반복 횟수 등의 정보를 제공) 인스턴스를 제공합니다.

  • TestReporterParamterResolver
    생성자나 메서드 파라미터가 TestReporter 타입이라면, TestReporterParamterResolver는 TestReporter 인스턴스를 제공합니다.
    TestReporter는 현재 테스트 실행에 대한 추가적인 데이터를 발행(publish)하는데 사용할 수 있습니다.

2.15. Repeated Tests

말 그대로 하나의 단일 테스트케이스를 반복해서 수행하는 어노테이션입니다.

사용방법

@RepeatedTest()를 이용해 반복 수행을 지시합니다.

예시

@RepeatedTest(10)
void repeatedTest() {
  // ...
}

2.16. Parameterized Tests

@ParameterizedTest 사용하면 다른 인수를 사용하여 테스트를 여러 번 실행할 수 있습니다.

사용 방법

@ParameterizedTest를 이용할 경우 호출에 대한 인수를 제공할 소스를 하나 이상 선언하여 사용합니다.

@ParameterizedTest
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
void palindromes(String candidate) {
  assertTrue(StringUtils.isPalindrome(candidate));
}

예시

@ValueSource

  • @ValueSource는 가장 심플한 사용법입니다. 하나의 배열로 값을 정의하며, 하나의 인자만 받는 파라미터화 테스트에만 적용할 수 있습니다.
@ParameterizedTest 
@ValueSource(ints = { 1, 2, 3 }) 
void testWithValueSource(int argument) {
	assertTrue(argument > 0 && argument < 4); 
}

Null and Empty Sources

잘못된 입력이 들어올 수 있는 경우를 확인하고 적절한 행동을 검증하기 위해서 파라미터화 테스트에 null 또는 빈 값을 제공해 줄 수 있습니다.

아래의 어노테이션을 통해 다음과 같은 값들을 제공합니다.
@NullSource : @ParameterizedTest 메소드에 null을 제공합니다.
@EmptySource : 다음과 같은 타입 String ,List ,Set, Map, primitive 배열 예) int[] ,char[] …, 객체 배열 예) String[] ,Intger[] … 같은 인자에 빈값을 제공합니다. 지원되는 타입중에 서브타입들은 지원하지 않습니다.
@NullAndEmptySource : @NullSource 와 @EmptySource 기능들을 합친 어노테이션입니다.

@ParameterizedTest 
@NullSource   // null 추가
@EmptySource  // 빈값 추가
@ValueSource(strings = { " ", " ", "\t", "\n" }) 
void nullEmptyAndBlankStrings(String text) {
  assertTrue(text == null || text.trim().isEmpty()); 
}

@EnumSource

Enum을 편리하게 사용할 수 있도록 도움을 주는 어노테이션 입니다.

@ParameterizedTest 
@EnumSource(mode = EXCLUDE, names = { "ERAS", "FOREVER" })
void testWithEnumSourceExclude(ChronoUnit unit) { 
  assertFalse(EnumSet.of(ChronoUnit.ERAS, ChronoUnit.FOREVER).contains(unit)); 
}

@MethodSource

@MethodSource는 하나 이상의 테스트 클래스 또는 외부 클래스 팩토리 메서드를 참조할 수 있습니다.

해당 어노테이션을 사용할 경우 다음과 같은 내용들을 확인해봐야 합니다.

  • @TestInstance(Lifecycle.PER_CLASS) 어노테이션을 테스트 클래스에 붙여야 합니다.
  • 호출하는 메소드에 반드시 static을 붙입니다.
  • 외부 클래스에 있는 팩토리 메서드를 사용할 경우 반드시 static을 붙입니다.(팩토리 메서드는 어떠한 인자도 있으면 안됩니다.)
  • 각각의 팩토리 메서드는 반드시 인자의 stream을 만들어야 하며, stream 안에 있는 각각의 인자들의 집합은 @ParameterizedTest 메서드의 각각의 호출마다 물리적 인자로 제공됩니다.

만약 하나의 파라미터만 필요하다면 다음과 같이 파라미터 타입의 인스턴스의 Stream을 리턴한다.

@ParameterizedTest 
@MethodSource("stringProvider") 
void testWithExplicitLocalMethodSource(String argument) {
  assertNotNull(argument); 
}

static Stream<String> stringProvider() {
  return Stream.of("apple", "banana"); 
}

@MethodSource를 통해서 팩토리 메소드 이름을 명시적으로 제공해주지 않을 경우 @ParamterizedTest 메소드가 붙은 현재 테스트 메소드 이름을 기준으로 팩토리 메서드를 찾습니다..

@CsvSource

@CsvSource는 리스트를 콤마(,)로 구분 해 준다.

@ParameterizedTest
@CsvSource({
  "apple, 1",
  "banana, 2",
  "'lemon, lime', 0xF1" 
})
void testWithCsvSource(String fruit, int rank) {
  assertNotNull(fruit);
  assertNotEquals(0, rank); 
}

기본 구분자는 콤마를 사용하며 delimiter 속성을 이용해서 다른 문자를 기본 구분자로 사용할 수 있습니다.
null이 리턴하는 대상 타입이 primitive 타입일 경우 ArgumentConversionException 예외를 발생시키니 조심하자.

예제 인풋Resulting Argument List
@CsvSource({ “apple, banana” })“apple”, “banana”
@CsvSource({ “apple, ‘lemon, lime’” })“apple”, “lemon, lime”

2.19. Timeouts

사용 방법

@Timeout 어노테이션을 사용하여 테스트마다 타임아웃 설정을 가능하게 합니다.

예제

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;

import java.util.concurrent.TimeUnit;

public class TimeOutExample1 {

    // timed out after 5 seconds
    @BeforeEach
    @Timeout(5)
    void setUpDB() throws InterruptedException {
        //TimeUnit.SECONDS.sleep(10);
    }

    // timed out after 500 miliseconds
    @Test
    @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
    void test_this() {
    }

}

2.20. Parallel Execution

JUnit Jupiter는 기본적으로 single thread로 순서대로 실행됩니다. junit 5 부터는 병렬 처리가 가능해지며 빠른 테스트 실행이 가능해집니다.

실행 종류

mode에는 두 가지 모드가 존재합니다.

SAME_THREAD

  • 상위에서 사용하는 것과 동일한 스레드에서 테스트를 강제 실행합니다.
  • test method는 @BeforeAll 또는 @AfterAll과 동일한 스레드에서 실행됩니다.

CONCURRENT

  • 동일한 스레드에서 강제 실행되지 않는 한 동시 실행

모드 설정시 테스트간 실행 순서는 다음과 같습니다.

사용 방법

테스트는 기본적으로 SAME_THREAD 실행 모드를 사용하고 있습니다. 병렬 처리를 희망할 경우 다음과 같은 설정이 필요합니다.

junit:
  jupiter:
    execution:
      parallel:
        enabled: true
          mode:
            default: 'concurrent' 
            classes:
              default: 'concurrent'           

모드 변경시 테스트 실행 순서 예시

3.5. Failure Message Arguments

JUnit Jupiter와 junit4는 AssumptionsAssertions 클래스가 다른 argument 순서를 가지고 있습니다.

Junit4

assertEquals(String message, Object
expected, Object actual)

JUnit Jupiter

assertEquals(Object expected, Object
actual, String message).

  • 사용시 실패 message에 대한 입력은 optional이며 이용시 주의가 필요합니다.
profile
Gelog 나쁜 것만 드려요~

0개의 댓글

관련 채용 정보