반응형

테스트 반복하기

  • 테스트를 매번 실행할 때마다 랜덤 값을 사용하거나, 테스트 실행 타이밍에 따라 달라질 수 있는 조건이 있는 경우에 테스트를 여러 번 반복적으로 실행하여 검증할 수 있다.

 

@RepeatedTest

  • 테스트 반복 횟수와 반복되는 테스트 이름을 설정할 수 있다.
    • {displayName} : @DisplayName 으로 지정된 값(@RepeatedTest 메서드의 표시 이름)
    • {currentRepetition} : 현재 반복 횟수
    • {totalRepetitions} : 총 반복 횟수
  • RepetitionInfo 타입의 인자를 받을 수 있다.

 

@ParameterizedTest

  • junit-jupiter-params 의존성을 추가해주어야 한다. 
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-params</artifactId>
    <version>5.5.2</version>
    <scope>test</scope>
</dependency>
  • 테스트에 여러 다른 매개변수를 대입해가며 반복 실행한다.
    • {displayName} : @DisplayName 으로 지정된 값
    • {index} : 현재 호출 인덱스(1-based)
    • {arguments} : 쉼표로 구분된 완전한 인수 목록
    • {0}, {1}, ... : arguments 인덱스 번호
      • 예) @CsvSource({"10, '자바 스터디'", "20, 스프링"})
      • {0} : 10, 20 출력
      • {1} : 자바 스터디, 스프링 출력

  • 인자 값들의 소스(Sources of Arguments)
    • @ValueSource
      • 리터럴 값의 단일 배열을 지정할 수 있으며 매개 변수화된 테스트 호출 시, 단일 인수를 제공하는 데만 사용할 수 있다.
    • @NullSource
      • null 주석이 달린 @ParameterizedTest 메서드에 단일 인수를 제공합니다 .
    • @EmptySource
      • 비어있는 문자열을 추가
      • 지원타입 목록
        • String
        • List
        • Set
        • Map
        • primitive arrays(int[], char[][], etc)
        • object arrays(String[], Integer[][], etc)
      • 지원되는 타입의 하위 타입은 지원하지 않는다.
        • 예) int[]는 지원하지만 int는 지원하지 않는다.
    • @NullAndEmptySource
      • @NullSource + @EmptySource
    • @EnumSource
      • Enum상수를 사용하는 편리한 방법을 제공한다.
    • @MethodSource
      • 테스트 클래스 또는 외부 클래스의 하나 이상의 팩토리 메소드를 참조할 수 있다 .
    • @CsvSource
      • 인수 목록을 쉼표로 구분된 값(즉, String리터럴) 으로 표현할 수 있다 .
      • 여러 인자를 전달
    • @CsvFileSource
      • 클래스 경로 또는 로컬 파일 시스템에서 CSV 파일을 사용할 수 있다.
      • CSV 파일의 각 행은 매개변수화된 테스트를 한 번 호출한다.
    • @ArgumentsSource
      • 재사용 가능한 사용자 지정 ArgumentsProvider를 지정하는 데 사용할 수 있다.

  • 인자 값 타입 변환(Argument Conversion)
    • 암묵적인 타입 변환(Implicit Conversion)
      • 선언된 소스에서 제공한 실제 유형이 String인 경우 문자열은 자동으로 변환된다. 

      • 문자열에서 위의 표에 나열된 대상 유형으로의 암시적 변환 외에도 JUnit Jupiter는 String대상 유형이 정의된 대로 정확히 하나의 적합한 팩토리 메소드 또는 팩토리 생성자 를 선언하는 경우 지정된 대상 유형으로의 자동 변환을 위한 폴백 메커니즘을 제공한다.
        • 팩토리 메소드 : 단일 String인수를 허용하고 대상 유형의 인스턴스를 반환하는 비공개가 아닌 static 메소드. 메소드 이름은 임의적일 수 있으며 특정 규칙을 따를 필요는 없다.
        • 팩토리 생성자 : 단일 String인수를 허용하는 대상 유형의 비공개가 아닌 생성자 . 대상 유형은 최상위 클래스(public Class) 또는 static 중첩 클래스 로 선언되어야 한다.
        • 만약, 여러 팩토리 메소드가 발견되면 무시된다.
        • 팩토리 메소드와 팩토리 생성자 둘 다 발견되면 생성자 대신 팩토리 메소드가 사용된다.


    • 명시적인 타입 변환(Explicit Conversion)
      • SimpleArgumentConverter를 상속 받은 구현체를 제공 해야한다.
        • 구현체는 public Class 또는 static Inner Class로 선언되어야 한다.
        • 해당 구현체를 이용하여 커스텀한 타입으로 변환할 수 있다.
        • 예) 숫자 → Study 타입으로
      • @ConvertWith
        • 특정 매개변수에 사용할 ArgumentConverter를 명시적으로 지정할 수 있다.
        • 예) @ConvertWith(StudyConverter.class)

  • 인자 값 집합(Argument Aggregation)
    • ArgumentsAccessor
      • 여러 인자 값을 ArgumentsAccessor를 통해서 사용할 수 있다.
      • 암시적 변환에서의 형식 변환이 지원된다.
    • 커스텀 Accessor 사용
      • ArgumentsAggregator 인터페이스 구현한다.
        • 반드시 public Class 또는 static Inner Class로 선언되어야 한다.
      • @AggregateWith
        • 커스텀 Accessor 사용 시, 어떤 Aggregator를 쓰겠다고 명시적으로 지정할 수 있다.
        • 예) @AggregateWith(StudyAggregator.class) Study study)

 

JUnit 5 - 테스트 반복하기 예제

  • Study
    public class Study {
    
        private StudyStatus status = StudyStatus.DRAFT;
        private int limit;
        private String name;
    
        public Study(int limit, String name) {
            this.limit = limit;
            this.name = name;
        }
    
        public Study(int limit) {
            if (limit < 0) {
                throw new IllegalArgumentException("limit은 0보다 커야 한다.");
            }
            this.limit = limit;
        }
    
        public Study() {
    
        }
    
        public StudyStatus getStatus() {
            return this.status;
        }
    
        public int getLimit() {
            return limit;
        }
    
        public String getName() {
            return name;
        }
    
        @Override
        public String toString() {
            return "Study{" +
                    "status=" + status +
                    ", limit=" + limit +
                    ", name='" + name + '\'' +
                    '}';
        }
    }

 

  • RepeatFastTest
    import org.junit.jupiter.api.RepeatedTest;
    import org.junit.jupiter.api.Tag;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Tag("fast")
    @RepeatedTest(value = 10, name = "{displayName} 반복, {currentRepetition}/{totalRepetitions}")
    public @interface RepeatFastTest {
    }

 

  • StudyTest
    		
        @RepeatFastTest
        void repeatTest(RepetitionInfo repetitionInfo){
            System.out.println("test" + repetitionInfo.getCurrentRepetition() + "/" + repetitionInfo.getTotalRepetitions());
        }
    
        @ParameterizedTest(name = "{index}. {displayName}, message={0}")
        @ValueSource(strings = {"날씨가", "많이", "더워지고", "있네요."})
        @NullAndEmptySource
        void parameterizedTest(String message){
            System.out.println(message);
        }
    
        @ParameterizedTest(name = "{index}. {displayName}, message={0}")
        @ValueSource(ints = {10, 20, 30})
        @NullSource
        void parameterizedTest2(Integer limit){
            System.out.println(limit);
        }
    
        /*
         * ValueSource를 Study 타입으로 받을 수도 있다.(인자 1개)
         */
        @ParameterizedTest(name = "{index}. {displayName}, message={0}")
        @ValueSource(ints = {10, 20, 30})
        void parameterizedTest3(@ConvertWith(StudyConverter.class) Study study){
            System.out.println(study.getLimit());
        }
        //SimpleArgumentConverter 상속 받은 구현체를 이용하여 파라미터를 특정 타입으로 변환 가능
        //SimpleArgumentConverter는 하나의 인자에만 적용할 수 있다.
        static class StudyConverter extends SimpleArgumentConverter{
            @Override
            protected Object convert(Object source, Class<?> targetType) throws ArgumentConversionException {
                assertEquals(Study.class, targetType, "Can only convert to Study");
                return new Study((Integer.parseInt(source.toString())));
            }
        }
    
        /*
         * CsvSource를 Study 타입으로 받을 수도 있다.(인자 2개)
         */
        //1. 두 개의 인자를 받아서 Study 생성
        @ParameterizedTest(name = "{index}. {displayName}, message={0}, {1}")
        @CsvSource({"10, '자바 스터디'", "20, 스프링"})
        void parameterizedTest4(Integer limit, String name){
            Study study = new Study(limit, name);
            System.out.println(study);
        }
    
        //2. ArgumentsAccessor를 이용해서 Study 생성
        @Tag("fast")
        @ParameterizedTest(name = "{index}. {displayName}, message={0}, {1}")
        @CsvSource({"30, 'HTTP'", "40, 스프링 부트"})
        void parameterizedTest5(ArgumentsAccessor argumentsAccessor){
            Study study = new Study(argumentsAccessor.getInteger(0), argumentsAccessor.getString(1));
            System.out.println(study);
        }
    
        //3. 커스텀 Accessor를 이용해서 Study 생성
        @ParameterizedTest(name = "{index}. {displayName}, message={0}, {1}")
        @CsvSource({"40, 'JPA'", "50, JUnit"})
        void parameterizedTest6(@AggregateWith(StudyAggregator.class) Study study){
            System.out.println(study);
        }
        //커스텀 Accessor
        static class StudyAggregator implements ArgumentsAggregator {
            @Override
            public Object aggregateArguments(ArgumentsAccessor argumentsAccessor, ParameterContext parameterContext) throws ArgumentsAggregationException {
                return new Study(argumentsAccessor.getInteger(0), argumentsAccessor.getString(1));
            }
        }

[참고자료]

더 자바, 애플리케이션을 테스트하는 다양한 방법, 백기선

JUnit 5 User Guide

반응형

+ Recent posts