테스트 코드/JUnit5

애노테이션

애노테이션

JUnit Jupiter에서는 프레임워크 상에서 테스트를 구성하고 확장하는데 도움이 되는 Annotation 들을 제공합니다,.

모든 핵심 Annotation들은 junit-jupiter-api 모듈 내의 org.junit.jupiter.api 패키지에 위치하고 있습니다.

 

@Test

@Test
void methodTest(){
    assertEquals("ddings", Home.name);
}

메소드가 테스트 메소드임을 나타냅니다. 

 

 

 

 

 

 

 

@ParameterizedTest

@ParameterizedTest(name = "{index} : Number => {0}")
@ValueSource(ints = {10,20,30,40})
void methodTest(int value){
    assertTrue(value > 0 && value < 50);
}

 

파라미터값을 이용한 2번 이상의 테스트실행이 가능한 메소드임을 나타냅니다.

name속성을 이용하여 테스트실행 시 명칭을 지정할 수 있습니다.

관련 문서

 

JUnit 5 User Guide

Although the JUnit Jupiter programming model and extension model will not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and cus

junit.org

 

매 순간 테스트 실행마다 제공될 파라미터를 제공하는 Source 애노테이션이 필요합니다.

 

Source 애노테이션은 기입한 Source 메소드 파라미터간에 일대일로 매칭됩니다.

Source의 정보들을 메소드의 객체 형태의 파라미터로 제공하는 것도 가능합니다.

ParameterResolver를 통한 추가적인 argument 들을 제공할 수 있습니다.

 

 

 

 

 

 

@ValueSoruce

리터럴 값의 배열로 선언하며 메소드 실행마다 하나의 파라미터를 제공할 수 있습니다.

 

지원하는 타입

  • short
  • byte
  • int
  • long
  • float
  • double
  • char
  • boolean
  • java.lang.String
  • java.lang.Class

 

 

 

 

 

@NullSource, @EmptySource, @NullAndEmptySource

@ParameterizedTest
@NullSource
void methodTest(String value){
    assertNull(value);
}

 

@NullSource

  • 하나의 Null 파라미터 자원을 제공합니다.
  • 프리미티브 타입에는 사용이 불가능합니다.
@ParameterizedTest
@EmptySource
void methodTest(List<Integer> numbers){
    for (Integer number : numbers) {
        System.out.println(number);
    }
}

 

@EmptySource

  • String, List, Set, Map, 프리미티브 타입 배열, 객체 배열 형태의 파라미터에 대한 빈 값을 제공합니다.
  • 위 타입들의 서브타입은 지원하지 않습니다.

 

@ParameterizedTest
@NullAndEmptySource
void methodTest(String next){
    System.out.println(next);
}

@NullAndEmptySource

  • @NullSource @EmptySource를 합친 애노테이션입니다.

 

 

 

 

 

 

@EnumSource

static enum Fruit{
    Apple(10),
    Banana(20),
    Citron(30);

    int value;

    Fruit(int value) {
        this.value = value;
    }
}

@ParameterizedTest
@EnumSource
void EnumSourceTest(Fruit fruit) {
    System.out.println(fruit + " : " + fruit.value);
}

 

Enum타입의 파라미터에 대한 자원을 제공합니다.

 

@ParameterizedTest
@EnumSource(Fruit.class)
void EnumSourceTest(Fruit fruit) {
    System.out.println(fruit + " : " + fruit.value);
}

Enum클래스를 value값으로 줄 수 있으며, 만일 value값이 없다면 파라미터에 첫 번째로 등장하는 enum타입에 매칭됩니다.

 

 

@ParameterizedTest
@EnumSource(mode = EnumSource.Mode.EXCLUDE, names = {"Apple", "Citron"})
void EnumSourceTest(Fruit fruit) {
    System.out.println(fruit + " : " + fruit.value);
}

mode 옵션과 names 옵션을 줄 수 있습니다.

 

 

mode 옵션의 종류

  • EXCLUDE : names 속성으로 주어진 값을 제외한 값을 가져옵니다.
  • INCLUDE : names 속성으로 주어진 값을 가져옵니다.
  • MATCH_ALL : names속성에 주어진 정규표현식의 조건들에 모두 해당하는 값을 가져옵니다.
  • MATCH_ANY : names속성에 주어진 정규표현식의 조건들에 하나로 해당하는 값을 가져옵니다.

 

 

 

 

 

@MethodSource

@ParameterizedTest
@MethodSource("stringProvider")
void MethodSourceTest(String str){
    System.out.println(str);
}

static Stream<String> stringProvider(){
    return Stream.of(
            "First",
            "Second",
            "Third"
    );
}

테스트 클래스외부클래스Factory 메소드들을 참조하는데 사용됩니다.

Factory 메소드는 @TestInstance(Lifecycle.PER_CLASS)이 선언되지 않았다면 static으로 선언되어야 합니다.

 

return 타입으로 지정할 수 있는 타입

  • Stream
  • DoubleStream
  • LongStream
  • IntStream
  • Collection
  • Iterator
  • Iterable
  • 객체 배열
  • 프리미티브 타입 배열

 

@ParameterizedTest
@MethodSource
void MethodSourceTest(String str){
    System.out.println(str);
}

static Stream<String> MethodSourceTest(){
    return Stream.of(
            "First",
            "Second",
            "Third"
    );
}

@MetohdSource에 Factory 메소드이름을 주지않으면 현재 @ParameterizedTest가 붙은 메소드동일한 이름의 팩토리 메소드가 매칭됩니다.

 

@ParameterizedTest
@MethodSource("StringAndintProvider")
void MethodSourceTest(String name, int value){
    System.out.println(name + " : " + value);
}


static Stream<Arguments> StringAndintProvider(){
    return Stream.of(
            Arguments.of("First", 1),
            Arguments.of("Second", 2)
    );
}

테스트메소드의 파라미터가 하나 이상이면 Collection, Stream, Arguments 인스턴스 배열 또는 객체 배열을 반환해야 합니다.

 

관련된 예시는 여기에서 확인할 수 있습니다.

 

MethodSource (JUnit 5.7.1 API)

@MethodSource is an ArgumentsSource which provides access to values returned from factory methods of the class in which this annotation is declared or from static factory methods in external classes referenced by fully qualified method name. Each factory m

junit.org

 

package me.ddings73.inflearnthejavatest;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.*;

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

class HomeTest {

    @ParameterizedTest
    @MethodSource("me.ddings73.inflearnthejavatest.Home#HomeProvider")
    void MethodSourceTest(Home home){
        System.out.println(home.getAge() + " : " + home.getJob());
    }
}


package me.ddings73.inflearnthejavatest;

import java.util.stream.Stream;

public class Home {
    static Stream<Home> HomeProvider(){
        return Stream.of(new Home(20, "Teacher"));
    }
}

 외부 클래스에있는 Factory 메소드는 FQMN( Fully Qualified Method Name ) 을 통하여 사용할 수 있습니다.

 

 

 

 

 

 

 

@CsvSource

@ParameterizedTest
@CsvSource({
        "First, 1",
        "Second, 2",
        "'Third, 세번째', 3",
        "'',4",
        ",5"
})
void CsvSourceTest(String Seq, int value){
    System.out.println(Seq + " : " + value);
}

다수의 파라미터를 가진 테스트메소드에 사용할 수 있는 애노테이션입니다.

, ( 콤마 ) 를 이용하여 순서를 구분하고 ' ( 작은 따옴표 ) 를 이용하여 하나의 덩어리를 판단합니다.

 

 

 

 

 

 

 

@CsvFileSource

@ParameterizedTest
@CsvFileSource(files = "src/test/resources/StringAndInteger.csv", numLinesToSkip = 1)
void CsvFileSourceTest(String menu, Integer price){
    System.out.println(menu + " : " + price);
}
menu, price
Americano, 2_000
CaffeLatte, 3_000
"Vanilla Bean Latte", 3_500

 

외부에 있는 CSV파일에서 파라미터값을 받아오는 애노테이션입니다. 

 

@CsvSource와 다르게 "( 큰 따옴표 )를 이용하여 하나의 덩어리를 구분합니다.

 

 

 

 

 

 

 

형 변환

확대 변환

@ParameterizedTest
@ValueSource(ints = {10,20,30})
void WideningTest(long value){
    System.out.println(value);
}

자바에서 제공하는 작은 타입 -> 큰 타입 으로의 변환을 지원합니다.

 

 

 

 

 

 

 

암시적 변환

@ParameterizedTest
@ValueSource(strings = "true")
void WideningTest(boolean flag){
    System.out.println(flag);
}

String의 특정 값에 대한 묵시적 변환을 제공합니다.

관련 문서

 

JUnit 5 User Guide

Although the JUnit Jupiter programming model and extension model will not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and cus

junit.org

문자열에서 객체로의 변환

 

JUnit 5 User Guide

Although the JUnit Jupiter programming model and extension model will not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and cus

junit.org

 

 

 

 

 

명시적 변환

 

명시적으로 형변환을 하기위해서는 ArguentConverter를 상속한 클래스의 convert 메소드를 이용해야합니다.

 

 

String 객체 파라미터로 받고싶을 때

@ParameterizedTest
@ValueSource(strings = {"Americano", "CaffeLatte", "Vanilla Bean Latte"})
void ConvertWithTest(@ConvertWith(ToCaffeConverter.class) Caffe guest){
    assertEquals(Caffe.class, guest.getClass());
    guest.showMenu();
}

public static class ToCaffeConverter extends SimpleArgumentConverter{

    @Override
    protected Object convert(Object source, Class<?> TargetType) throws ArgumentConversionException {
        assertEquals(Caffe.class, TargetType, "Can only convert to Caffe");
        return new Caffe(String.valueOf(source));
    }
}

 

 

 

 

 

특정 타입으로의 변환을 하고 싶을때

@ParameterizedTest
@ValueSource(strings = {
        "Americano",
        "CaffeLatte",
        "Vanilla Bean Latte"
})
void ConvertWithTest(@ConvertWith(ToMenuLengthConverter.class) Integer length){
    System.out.println(length);
}

public static class ToMenuLengthConverter extends TypedArgumentConverter<String, Integer>{
    protected ToMenuLengthConverter() {
        super(String.class, Integer.class);
    }

    @Override
    public Integer convert(String source) throws ArgumentConversionException {
        return source.length();
    }
}

 

 

 

 

 

한번에 들어오는 둘 이상의 데이터 객체의 형태로 변환하여 받고싶을 때

@ParameterizedTest
@CsvSource({
        "Americano, 2500",
        "CaffeLatte, 3000",
        "'Vanilla Bean Latte', 3500"
})
void ConvertWithTest(@AggregateWith(ToOrderConverter.class) Caffe guest){
    assertEquals(Caffe.class, guest.getClass());
    guest.showOrder();
}

public static class ToOrderConverter implements ArgumentsAggregator {

    @Override
    public Object aggregateArguments(ArgumentsAccessor argumentsAccessor, ParameterContext parameterContext) throws ArgumentsAggregationException {
        return new Caffe(
                argumentsAccessor.getString(0),
                argumentsAccessor.getInteger(1)
        );
    }
}

 

 

 

 

 

@RepeatedTest

@RepeatedTest(value = 10, name = "RepeatedTest : {currentRepetition} / {totalRepetitions}")
void RepeatTest(){

}

 

원하는 횟수만큼 테스트를 반복시키는 애노테이션입니다. 

 

value반복 횟수를 지정하고 name으로 표기될 이름을 지정할 수 있습니다.

 

{currentRepetition} : 현재 반복 횟수

{totalRepetitions} : 총 반복 횟수

 

 

 

 

 

@DisplayName

@Test
@DisplayName("DisplayName 테스트")
void DisplayTest(){

}

테스트 메소드의 표기이름을 지정합니다.

 

 

 

 

 

@DisplayNameGeneration

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class CaffeTest {

    @Test
    void Test_Method_01(){
        
    }
    
    @Test
    void Test_Method_02(){

    }
}

테스트 클래스내의 모든 메소드의 이름에 적용되는 규칙을 지정합니다.

 

규칙 행동
Standard Junit Jupiter 5.0이후 제공되는 기본형식대로 출력
Simple 파라미터가 없는 매소드의 () 삭제
ReplaceUnderscores _ 를 공백으로 치환
IndicativeSentences 클래스명과 함께 표기

 

 

 

 

LifeCycle Method

후술할 애노테이션이 달린 메소드를 LifeCycle Method라고 부릅니다.

@BeforeEach, @BeforeAll

@BeforeEach
@DisplayName("각 메소드 실행 전에 호출")
void BeforeEach(){
    System.out.println("BeforeEach");
}

@BeforeAll
@DisplayName("모든 메소드 실행 전에 호출")
static void BeforeAll(){
    System.out.println("BeforeAll");
}

 

 

@BeforeEach : 각각의 테스트 메소드가 실행되기 전에 호출

@BeforeAll : static으로 선언되어야하며, 테스트 시작에 앞서 단 한번 호출 

 

 

 

 

 

@AfterEach, @AfterAll

@AfterEach
@DisplayName("각 메소드 실행 후에 호출")
void AfterEach(){
    System.out.println("AfterEach");
}

@AfterAll
@DisplayName("모든 메소드 실행 후에 호출")
static void AfterAll(){
    System.out.println("AfterAll");
}

@AfterEach : 각각의 테스트 메소드가 끝날 때마다 호출

@AfterAll : static으로 선언되어야하며, 모든 테스트가 끝나면  단 한번 호출

 

 

 

 

 

 

@Tag

@Test
@Tag("테스트_메소드")
void Test_Method_01(){

}

@Test
@Tag("테스트_메소드")
void Test_Method_02(){

}

IntelliJ에서는 Run/Debug Configuration에 들어가서 원하는 태그가 붙은 메소드만 실행하는 것이 가능합니다. 

Maven에서 태그를 필터링하기 위해서는 surefire 플러그인이 필요합니다.

더보기
    <profiles>
        <profile>
            <id>default</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <configuration>
                            <groups>테스트_메소드</groups> <!-- 필터링할 태그 -->
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

 

Tag를 설정할 때, 포함되지 않아야하는 문자는 다음과 같습니다.

  • 공백
  • ,
  • (
  • )
  • &
  • |
  • !

 

 

 

 

@Disabled, @Enabled

@Test
@Disabled("실행되지 않는 테스트")
void DIsabled(){

}


@Test
@DisplayName("윈도우에서만 실행")
@EnabledOnOs(OS.WINDOWS)
void OnlyWindows(){
    assertTrue(true);
}

@Test
@DisplayName("윈도우와 맥에서 실행 불가능")
@DisabledOnOs({OS.WINDOWS, OS.MAC})
void DisabledWIN_MAC(){
    assertTrue(true);
}

 

 

 

특정 환경이나 상황에서 실행되거나 되지않을 메소드를 지정합니다.

 

애노테이션  기능
@Disabled 실행되지 않는 테스트를 의미합니다. 
@DisabledOnOs 특정 OS에서 테스트의 실행을 금지합니다.
@DisabledOnJre 특정 자바 버전에서 테스트의 실행을 금지합니다.
@DisabledForJreRange 실행되지 않는 자바버전의 범위로 지정합니다.
@EnabledOnOs 특정 OS에서만 테스트가 실행됩니다.
@EnabledOnJre 특정 자바 버전에서만 테스트가 실행됩니다.
@EnabledForJreRange 지원하고 싶은 자바 버전의 범위를 지정합니다.

 

참고 문서

 

JUnit 5 User Guide

Although the JUnit Jupiter programming model and extension model will not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and cus

junit.org

 

 

 

 

 

 

@TestMethodOrder와 @Order

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class CaffeTest {

    @Test
    @Order(0)
    @DisplayName("첫 번째로 실행 될 테스트")
    void First(){
        System.out.println("First");
    }


    @Test
    @Order(10)
    @DisplayName("세 번째로 실행 될 테스트")
    void Second(){
        System.out.println("Second");
    }



    @Test
    @Order(30)
    @DisplayName("두 번째로 실행 될 테스트")
    void Third(){
        System.out.println("Third");
    }

}

 

 

테스트의 실행순서를 지정하는 애노테이션으로 작은 숫자에서 큰 숫자 순서대로 실행됩니다.

실행 순서를 지정하지 않았을 경우, 특정한 로직으로 정해진 순서에 따라 실행됩니다. 

  • 기본적으로 단위테스트 사이에는 의존성이 없어야함

 

 

 

 

@TestInstance

@DisplayName("카페 테스트")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class CaffeTest {

    Caffe caffe = new Caffe("Americano", 1_500);


    @Test
    @Order(0)
    @DisplayName("첫 번째로 실행 될 테스트")
    void First(){
        System.out.println(caffe);
    }


    @Test
    @Order(10)
    @DisplayName("세 번째로 실행 될 테스트")
    void Second(){
        System.out.println(caffe);
    }



    @Test
    @Order(30)
    @DisplayName("두 번째로 실행 될 테스트")
    void Third(){
        System.out.println(caffe);
    }

    @BeforeAll
    @DisplayName("실행 전")
    static void BeforeALL(){
        System.out.println("BeforeAll");
    }

    @AfterAll
    @DisplayName("실행 후")
    static void AfterALL(){
        System.out.println("AfterAll");
    }
}

기본적으로 테스트 클래스의 동일한 필드를 사용하더라도 테스트 간의 의존성을 없애기 위하여 각각의 테스트마다 서로 다른 객체를 사용합니다. 

 

 

 

 

 

@DisplayName("카페 테스트")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class CaffeTest {

    Caffe caffe = new Caffe("Americano", 1_500);


    @Test
    @Order(0)
    @DisplayName("첫 번째로 실행 될 테스트")
    void First(){
        System.out.println(caffe);
    }


    @Test
    @Order(10)
    @DisplayName("세 번째로 실행 될 테스트")
    void Second(){
        System.out.println(caffe);
    }



    @Test
    @Order(30)
    @DisplayName("두 번째로 실행 될 테스트")
    void Third(){
        System.out.println(caffe);
    }

    @BeforeAll
    @DisplayName("실행 전")
    void BeforeALL(){
        System.out.println("BeforeAll");
    }

    @AfterAll
    @DisplayName("실행 후")
    void AfterALL(){
        System.out.println("AfterAll");
    }
}

@TestInstance를 사용하여 라이프 사이클을 지정해주면 필드값을 테스트 클래스 내의 모든 테스트가 공유하게 됩니다.

추가적으로 @BeforeAll 또는 @AfterAll 사용시에 static를 지정해주지 않아도 됩니다. 

 

라이프 사이클은 PER_METHOD, PER_CLASS가 존재합니다.

 

 

 

 

 

 

 

 

커스텀 애노테이션

 

JUnit의 애노테이션들은 Meta 애노테이션을 지원하기 때문에 사용자가 원하는 애노테이션을 생성할 수 있습니다.

 

package me.ddings73.inflearnthejavatest;

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ParameterizedTest
@ValueSource(strings = {"Americano", "CaffeLatte", "Vanilla Bean Latte"})
@Tag("ShowMenu")
public @interface MenuTest {

}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class CaffeTest {

    Caffe caffe = new Caffe("Americano", 1_500);


    @MenuTest
    @DisplayName("메뉴 소개")
    void showMenu(String menu){
        System.out.println(menu);
    }

    @BeforeAll
    @DisplayName("실행 전")
    void BeforeALL(){
        System.out.println("BeforeAll");
    }

    @AfterAll
    @DisplayName("실행 후")
    void AfterALL(){
        System.out.println("AfterAll");
    }
}

 

 

 

 

 

 

 

@Nested

@DisplayName("카페 테스트")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class CaffeTest {

    @BeforeAll
    @DisplayName("카페 테스트를 시작합니다.")
    void BeforeALL(){
    }

    @AfterAll
    @DisplayName("카페 테스트를 종료합니다.")
    void AfterALL(){
    }

    @Nested
    @DisplayName("메뉴 소개")
    class MenuList {

        @MenuTest
        @DisplayName("메뉴 소개")
        void showMenu(String menu) {
            System.out.println(menu);
        }

        @BeforeEach
        @DisplayName("메뉴 소개를 시작합니다.")
        void BeforeEach() {

        }

        @AfterEach
        @DisplayName("메뉴 소개를 종료합니다.")
        void AfterEach() {

        }

        @Nested
        @DisplayName("가격 소개")
        class priceTest {

            @PriceTest
            @DisplayName("메뉴 별 가격 소개")
            void showPrice(String menu, int price){
                System.out.println(menu + " : " + price);
            }

            @BeforeEach
            @DisplayName("가격 소개를 시작합니다.")
            void BeforeEach() {

            }

            @AfterEach
            @DisplayName("가격 소개를 종료합니다.")
            void AfterEach() {

            }
        }
    }
}

 

 

테스트간의 관계를 표현하는 애노테이션이며, 오직 클래스에만 붙힐 수 있습니다.

 

 

 

 

 

 

출처

JUnit 5 User Guide

인프런 - 더 자바, 애플리케이션을 테스트하는 다양한 방법