JUnit

이연중·2021년 2월 5일
0

JAVA

목록 보기
15/20

JUnit5 소개

자바 개발자가 가장 많이 사용하는 테스팅 프레임워크

  • 자바8 이상을 필요로 함
  • 대체: TestNG, Spock 등

JUnit5의 세부 모듈

  • Platform: 테스트를 실행해주는 런처 제공(TestEngine API 제공)
  • Jupiter: TestEngine API 구현체로 JUnit5를 제공
  • Vintage: JUnit4와 3을 지원하는 TestEngine 구현체

JUnit5: 시작하기

  • 2.2+ 버전의 스프링 부트 프로젝트를 만든다면 기본으로 JUnit 의존성이 추가

스프링 부트 프로젝트를 사용하지 않는다면?

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

기본 어노테이션

  • @Test
  • @BeforeAll/@AfterAll
  • @BeforeEach/@AfterEach
  • @Disabled
	@Test
    void create(){
        Study study=new Study();
        assertNotNull(study);
        System.out.println("create");
    }
    
    @Test
    @Disabled //테스트가 실행되지 않음
    void create1(){
        System.out.println("create1");
    }

    //모든 테스트 수행전 딱 한번. 반드시 static 메서드를 사용해야함.(default는 되고, return은 없어야됨)
    @BeforeAll
    static void beforeAll(){
        System.out.println("before all");
    }

    //모든 테스트 실행후 딱 한번
    @AfterAll
    static void afterAll(){
        System.out.println("after all");
    }

    //각각의 테스트 수행전 딱 한번
    @BeforeEach
    void beforeEach(){
        System.out.println("Before each");
    }

    //각각의 테스트 수행후 딱 한번
    @AfterEach
    void afterEach(){
        System.out.println("after each");
    }

JUnit5: 테스트 이름 표시하기

@DisplayNameGeneration

  • Method와 Class 레퍼런스를 사용해 테스트 이름을 표기하는 방법 설정
  • 기본 구현체로 ReplaceUnderscores 제공
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class StudyTest {

    @Test
    void create_new_study(){
        Study study=new Study();
        assertNotNull(study);
        System.out.println("create");
    }

    @Test
    void create_new_study_again(){
        System.out.println("create1");
    }
}

@DisplayName

  • 어떤 테스트인지 테스트 이름을 보다 쉽게 표현할 수 있는 방법을 제공하는 어노테이션
  • @DisplayNameGeneration보다 우선순위 높음
	@Test
    @DisplayName("스터디 만들기 \uD83D\uDE31")
    void create_new_study(){
        Study study=new Study();
        assertNotNull(study);
        System.out.println("create");
    }

JUnit5: Assertion

org.junit.jupiter.api.Assertions.*

실제 값이 기대한 값과 같은지 확인assertEquals(expected, actual)
값이 null이 아닌지 확인asserNotNull(actual)
다음 조건이 참인지 확인assertTrue(boolean)
모든 확인 구문 확인assertAll(executables...)
예외 발생 확인assertThrows(expectedType, executable)
특정 시간 안에 실행이 완료되는지 확인assertTimeout(duration, executable)
void create_new_study(){
        Study study=new Study(-10);

        assertAll(
                ()->assertNotNull(study),
        //첫번째 파라미터: 기대값, 두번째 파라미터: 실제값, 세번쨰 파라미터: 오류 발생시 메시지(람다식으로 만들면 실패 했을때만 문자열 연산 수행하고, 람다식으로 만들지 않을시 무조건 문자열 연산을 수행한다)
                ()->assertEquals(StudyStatus.ENDED, study.getStatus(), () -> "스터디를 처음 만들면 상태값이 DRAFT이어야 한다."),
                ()->assertTrue(study.getLimit()>0,"스터디 최대 참석 가능 인원은 0보다 커야 한다.")
        );
    
    	assertThrows(IllegalArgumentException.class,()->new Study(-10));

        assertTimeout(Duration.ofMillis(100),()->{
            new Study(10);
            Thread.sleep(300);
        });

        System.out.println("create");
    }

마지막 매개변수로 Supplier 타입의 인스턴스를 람다 형태로 제공할 수 있음

  • 복잡한 메시지를 생성해야 하는 경우 사용하면 실패한 경우에만 해당 메시지를 만들게 할 수 있음

AssertJ, Hemcrest, Truth 등의 라이브러리도 사용 가능

JUnit5: 조건에 따라 테스트 실행하기

특정한 조건을 만족하는 경우 테스트를 실행하는 방법

org.junit.jupiter.api.Assumptions.*

  • assumeTrue(조건)

  • assumingThat(조건, 테스트)

  • 	void create_new_study() throws InterruptedException {
            String test_env=System.getenv("TEST_ENV");
            System.out.println(test_env);
            assumeTrue("LOCAL".equalsIgnoreCase(test_env));
    
            assumingThat("LOCAL".equalsIgnoreCase(test_env),()->{
                System.out.println("local");
                Study actual=new Study(100);
            });
    
            assumingThat("always".equalsIgnoreCase(test_env),()->{
                System.out.println("always");
                Study actual=new Study(10);
            });
        }

@Enabled와 @Disabled

  • OnOS
  • OnJre
  • IfSystemProperty
  • IfEnvironmentVariable
    • @EnabledIfEnvironmentVariable(named="TEST_ENV",matches="local")
  • If

JUnit5: 태깅과 필터링

테스트 그룹을 만들고 원하는 테스트 그룹만 테스트를 실행할 수 있는 기능

@Tag

  • 테스트 메소드에 태그를 추가할 수 있음
  • 하나의 테스트 메소드에 여러 태그를 사용할 수 있음

인텔리제이에서 특정 태그로 테스트 필터링

클릭후 "Edit Configuration" 클릭!

메이븐에서 테스트 필터링

<profiles>
 <profile>
  <id>default</id>
  <activation>
    <activativeByDefault>true</activativeByDefault>
  </activation>
  <build>
   <plugins>
    <plugin>
     <artifactId>maven-surefire-plugin</artifactId>
     <configuration>
     <groups>fast | slow</groups>
     </configuration>
	</plugin>
   </plugins>
  </build>
 </profile>
</profiles>

JUnit5: 커스텀 태그

JUnit5 어노테이션을 조합해 커스텀 태그를 만들 수 있음

SlowTest.java

@Target(ElementType.METHOD) //해당 어노테이션은 메소드에 쓸 수 있음
@Retention(RetentionPolicy.RUNTIME) //해당 어노테이션의 정보를 런타임까지 기억
@Test
@Tag("slow")
public @interface SlowTest {
}
 	@SlowTest
    @DisplayName("스터디 만들기 slow")
    void create_new_study_again(){
        System.out.println("create1");
    }

이렇게 하면 태그를 붙이지 않고, 해당 어노테이션을 가져다 쓰기만 하면 됨

JUnit5: 테스트 반복하기

@RepeatedTest

  • 반복 횟수와 반복 테스트 이름을 설정할 수 있음

    • {displayName}
    • {currentRepetition}
    • {totalRepetitions}
  • RepetitionInfo 타입의 인자를 받을 수 있음

  •  	@DisplayName("스터디 만들기")
         @RepeatedTest(value=10,name="{displayName},{currentRepetition}/{totalRepetitions}")
         void repeatTest(RepetitionInfo repetitionInfo){
             System.out.println("test "+repetitionInfo.getCurrentRepetition()+"/"+repetitionInfo.getTotalRepetitions());
         }

@ParameterizedTest

  • 테스트에 여러 다른 매개변수를 대입하며 반복 실행

    • {displayName}
    • {index}
    • {arguments}
    • {0},{1}, 등
  • 	@DisplayName("스터디 만들기")
        @ParameterizedTest(name="{index} {displayName} msg={0}")
        @ValueSource(strings={"날씨가","많이","추워지고","있네요"})
        void parameterizedTest(String msg){
            System.out.println(msg);
        }

@ParameterizedTest의 인자 값들의 소스

  • @ValueSource: 하나의 인자 넘겨줌
  • @CvsSource: 여러 인자 넘겨줌
  • @EmptySource: 비어있는 문자열 인자로 추가 테스트
  • @NullSource: 널을 인자로 추가 테스트
  • @NullAndEmptySource

@ParameterizedTest의 인자 값 타입 변환

  • 암묵적인 타입 변환

  • 명시적인 타입 변환

    • SimpleArgumentConverter 상속 받은 구현체 제공

    • @ConvertWith

    • 	@DisplayName("스터디 만들기")
          @ParameterizedTest(name="{index} {displayName} msg={0}")
          @ValueSource(ints={10,20,40})
          void parameterizedTest(@ConvertWith(StudyConverter.class)Study study){
              System.out.println(study.getLimit());
          }
          
          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()));
              }
          }

@ParameterizedTest의 인자 값 조합

  • ArgumentsAccessor

    	@DisplayName("스터디 만들기")
        @ParameterizedTest(name="{index} {displayName} msg={0}")
        @CsvSource({"10 '자바 스터디'","20,스프링"})
        void parameterizedTest(ArgumentsAccessor argumentsAccessor){
            Study study=new Study(argumentsAccessor.getInteger(0),argumentsAccessor.getString(1));
            System.out.println(study);
        }
  • 커스텀 Accessor

    • ArgumentsAggregator 인터페이스 구현
    • @AggregateWith
    	@DisplayName("스터디 만들기")
        @ParameterizedTest(name="{index} {displayName} msg={0}")
        @CsvSource({"10 '자바 스터디'","20,스프링"})
        void parameterizedTest(@AggregateWith(StudyAggregator.class)Study study){
            System.out.println(study);
        }
    
        static class StudyAggregator implements ArgumentsAggregator{
            @Override
            public Object aggregateArguments(ArgumentsAccessor argumentsAccessor, ParameterContext parameterContext) throws ArgumentsAggregationException {
                return new Study(argumentsAccessor.getInteger(0),argumentsAccessor.getString(1));
            }
        }

JUnit5: 테스트 인스턴스

JUnit은 테스트 메소드마다 테스트 인스턴스를 새로 만듦

  • 테스트 메소드를 독립적으로 실행해 예상치 못한 부작용을 방지하기 위함
  • 이를 JUnut5에서 변경 가능

@TestInstance(Lifecycle.PER_CLASS)

  • 테스트 클래스당 인스턴스를 하나만 만들어 사용
  • 경우에 따라 테스트 간 공유하는 모든 상태를 @BeforeEach 또는 @AfterEach에서 초기화 할 필요 있음
  • @BeforeAll과 @AfterAll을 인스턴스 메소드 또는 인터페이스에 정의한 default 메소드로 정의할 수도 있음(또한 이들이 Static 메소드로 선언되지 않아도 됨)

JUnit5: 테스트 순서

실행할 테스트 메소드는 특정한 순서에 의해 실행되지만, 경우에 따라 테스트 메소드를 원하는 순서에 따라 실행할 수 있다

@TestMethodOrder

  • 테스트 클래스당 하나의 인스턴스를 만들고

  • MethodOrderer 구현체 설정

  • 기본 구현체

    • alphanumeric
    • OrderAnnotation
    • Random
  • @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
    @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
    class StudyTest {
        @Order(1)
        @EnabledOnOs(OS.WINDOWS)
        @Test
        @Tag("fast")
        void create_new_study() throws InterruptedException {
            String test_env=System.getenv("TEST_ENV");
            System.out.println(test_env);
        }

JUnit5: junit-platform.properties

JUnit 설정 파일로 classpath root(src/test/resources/)에 넣어두면 적용됨

전체 테스트에 일괄 설정됨

Project Structure에 가서 해당 파일을 Test Resources 파일로 등록해야함

테스트 인스턴스 라이프사이클 설정

junit.jupiter.testinstance.lifecycle.default=per_class

확장팩 자동 감지 기능
junut.jupiter.extensions.autodetection.enabled=true

@Disabled 무시하고 실행하기
junit.jupiter.conditions.deactivate=org.junit.*DisabledCondition

테스트 이름 표기 전략 설정

junit.jupiter.displayname.generator.default=\org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores

JUnit5: 확장 모델

JUnit4의 확장 모델은 @RunWith(Runner), TestRule, MethodRule

JUnit5의 확장 모델은 Extension

확장팩 등록 방법

  • 선언적인 등록 @ExtendWith(파라미터 전달 불가)

  • 프로그래밍 등록 @RegisterExtension(파라미터 전달 가능)
    등록하고자 하는 클래스 필드에 작성
    @RegisterExtension

    static FindSlowTestExtension findSlowTestExtension= new FindSlowTestExtension(1000L);

  • 자동 등록 자바 ServiceLoader 이용

    • junut.jupiter.extensions.autodetection.enabled=true를 resources 파일에 추가

확장팩 만드는 법

  • 테스트 실행 조건
  • 테스트 인스턴스 팩토리
  • 테스트 인스턴스 후-처리기
  • 테스트 매개변수 리졸버
  • 테스트 라이프사이클 콜백
  • 예외처리

JUnit5: JUnit4 마이그레이션

  • junit-vintage-engine을 의존성으로 추가하면, JUnit5의 junit-platform으로 JUnit3과 4로 작성된 테스트를 실행할 수 있음
  • @Rule은 기본적으로 지원하지 않지만, junit-jupiter-migrationsupport 모듈이 제공하는 @EnableRuleMigrationSupport를 사용하면 다음 타입의 Rule을 지원
    • ExternalResource
    • Verifier
    • ExpectedException
JUnit4JUnit5
@Category(Class)@Tag(String)
@RunWith, @Rule, @ClassRule@ExtendWith, @RegisterExtension
@Ignore@Disabled
@Before, @After, @BeforeClass, @AfterClass@BeforeEach, @AfterEach, @BeforeAll, @AfterAll

참고

https://www.inflearn.com/course/the-java-application-test

profile
Always's Archives

0개의 댓글