Junit5

이한수·2022년 7월 24일
0

TEST

목록 보기
2/3
post-thumbnail

참고 : https://junit.org/junit5/docs/current/user-guide/

https://velog.io/@ynjch97/JUnit5-JUnit5-%EA%B5%AC%EC%84%B1-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-Assertions-%EC%A0%95%EB%A6%AC

https://velog.io/@jaehoonlee/JUnit-5-%EA%B3%B5%EC%8B%9D-%EA%B0%80%EC%9D%B4%EB%93%9C-%EB%AC%B8%EC%84%9C-%EC%A0%95%EB%A6%AC

https://donghyeon.dev/junit/2021/04/11/JUnit5-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C/

https://howtodoinjava.com/junit5/junit-5-assertions-examples/

개요(Overview)

JUnit 5의 구조

JUnit Platform + JUnit Jupiter + JUnit Vintage

Junit Platfrom

  • JVM에서 테스트 프레임워크를 실행하는데 기초를 제공합니다.
  • TestEngine API를 제공해 테스트 프레임워크를 개발할 수 있다

JUnit Jupiter

  • Unit 5에서 테스트를 작성하고 확장을 하기 위한 새로운 프로그래밍 모델과 확장 모델의 조합입니다.

JUnit Vintage

  • 하위 호환성을 위해 JUnit3과 JUnt4를 기반으로 돌아가는 플랫폼에 테스트 엔진을 제공해줍니다.

Junit5는 Java8 버전이상을 필요로 합니다.

기본 어노테이션

  • @DisplayName
    • 테스트 클래스나 테스트 메소드에 이름을 붙여줄 때 사용
  • @DisplayNameGeneration이 있을 경우 , DisplayName이 우선 적용된다.

    • 클래스에 해당 애노테이션을 붙이면 @Test 메소드 이름에 언더바(_)로 표시한 모든 부분을 아래와 같이 표시.
  • Standard : 메소드이름과 괄호 까지 표시

  • Simple : 메소드 이름만

  • ReplaceUnderscores : 언더스코어를 제거하고 공백으로.

  • IndicativeSentenses : 클래스 이름 + 메소드 이름 + 괄호

ex)
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)의 경우

  • test_service -> test service

참고
만일 , DisplayName과 DisplayNameGeneration이 있을 경우 , DisplayName이 우선 적용된다.

  • @BeforeEach
    • 각각 테스트 메소드가 실행되기전에 실행
    • @Test , @RepeatedTest , @ParameterizedTest , @TestFactory 가 붙은 테스트 메소드가 실행하기 전에 실행된다.
  • @AfterEach
    • @Test , @RepeatedTest , @ParameterizedTest , @TestFactory 가 붙은 테스트 메소드가 실행되고 난 후
  • @BeforeAll
    • 테스트가 시작하기 전 딱 한 번만 실행
  • @AfterAll
    • 테스트가 완전히 끝난 후 딱 한 번만 실행

BeforeAll , AfterAll 는 static 메소드로 작성해야 한다.
class에 @TestInstance(TestInstance.Lifecycle.PER_CLASS)라고 붙여준다면, static 메소드가 아니어도 동작한다.

  • @Nested

    • test 클래스안에 Nested 테스트 클래스를 작성할 때 사용되며, static이 아닌 중첩클래스, 즉 Inner 클래스여야만 한다.
  • @Tag

    • 테스트를 필터링할 때 사용한다
    • 클래스또는 메소드레벨에 사용
  • @Disable

    • 클래스나, 메소드의 테스트를 비활성화 한다
    • @Disabled 를 사용할 때 이유를 명시하지 않고 사용해도 되지만, JUnit 팀에서는 왜 이 테스트가 비활성화 되었는지에 대한 짧은 설명을 추가하는 걸 권장하고 있다
    
      @Test
      @Disabled("깨진 테스트 이유를 알기전까지는 비활성화") 
      void disabledTest(){
          System.out.println("이걸 실행되면 안됨");
      }
    
  • @Timeout

    • 주어진 시간안에 테스트가 끝나지 않으면 실패한다.
  • @ExtendWith

    • extension을 등록
    • 이 어노테이션은 상속이 된다.
  • @RegisterExtension

    • 필드를 통해 extension을 등록한다. 이런 필드는 private이 아니라면 상속

    반복 테스트 하기

  • @RepeatedTest

    • 테스트를 반복할 수 있다.
      @RepeatedTest(10)
      void repeatTest(){
          System.out.println("test");
      }
      //테스트 10번 반복
    
      @RepeatedTest(10)
      void repeatTest2(RepetitionInfo repetitionInfo){
          System.out.println(repetitionInfo.getCurrentRepetition() + "/" + repetitionInfo.getTotalRepetitions());
      }
      //테스트 10번 반복
      //RepetitionInfo 를 이용하여 현재 반복횟수와 총 반복회수를 가져올 수 있다.
      
      @DisplayName("스터디 만들기")
      @RepeatedTest(value =10 , name ="{displayName}, {currentRepetition}/{totalRetetitions}")
      void repeatTest(RepetitionInfo repetitionInfo){
      	System.out.println("test");
      }
      //value로 테스트 반복 횟수
      //name으로 반복되는 테스트마다 이름을 붙여줄 수 있다.
      
  • @ParameterizedTest

    • 반복 되는 테스트 마다 따른 값을 이용하고 싶은 경우.

      1) @ValueSource 이용하기

      @DisplayName("파리미터 테스트")
      @ParameterizedTest(name= {index} {displayName} message = {0})
      @ValueSource(strings= {"날씨가" , "너무" ,"덥네요"})
      void parameterizedTest(String message){
      		System.out.println(message);
      }
      
      //해당 인자값들을 message로 바인딩 받아서 사용한다.
      //index를 이용하여 횟수를 알 수 있고, 해당 인덱스로 테스트에서 사용하는 인자값을 참조할 수 있다.
      
      //ints등 다양한 Primitive type을 지원한다.
      //하나의 인수만을 전달할 때 사용한다는 점 주의.
      
      
      //다른 예시 객체로 받기
      static class Study{
      		String username;
      }
      
      staic class StudyConverter extends SimpleArgumentConverter{
      		
           @Override
           protected Object convert(Object source, Class<?> targetType) throws ArgumentConverter
      
      		 Assertions.assertEquals(Study.class,targetType);
               return new Study(source.toString());
      	 }
      
      @ParameterizedTest(name= {index} {displayName} message = {0})
      @ValueSource(strings= {"홍길동" , "홍길동2" ,"홍길동3"})
      void parameterizedTest(@ConvertWith(StudyConverter.class) Study study){
      		System.out.println(study.getName());
      }
      
      //study처럼 custom한 객체로 받고 싶다면 , 
      SimpleArgumentConverter를 상속받아 구현하여 처리해줘야 한다.
      
      2) @NullSource && @EmptySource
      
      > 주의 @NullSource
      
      ```java
      @ParameterizedTest
      @ValueSource(strings= {"날씨가" , "너무" ,"덥네요"})
      @EmptySource
      @NullSource
      void parameterizedTest(String message){
      		System.out.println(message);
      }
      
      //빈값을 넘겨주어 한번 호출해준다.
      //null값을 넘겨 한번 호출한다.
      
      //총 5번의 테스트가 호출된다.
      //2개를 붙힌 @NullAndEmptySource를 이용해도 같은 결과다.
      //참고 , @NullSource는 Primitive type에는 null값이 들어갈 수 없어서 실패한다.
      
      ```

      3) @CsvSource

      //여러개의 인자값을 넘겨주고자 할 때 사용할 수 있다.
      
      //예시1
      static class Study{
      		int age;
           String username;
           
           public Study(String username , int age){
           		this.username = username;
                   this.age = age;
           }
       }
      
      
      @ParameterizedTest
      @CsvSource({"홍길동" , "10" ,"홍길동2" , "20"})
      void parameterizedTest(String username , int age ){
      	Study study = new Study(username, age);
      		System.out.println(study.getName());
      }
      
      
      //예시2
      @ParameterizedTest
      @CsvSource({"홍길동" , "10" ,"홍길동2" , "20"})
      void parameterizedTest(ArgumentsAccessor arguments){
      	Study study = new Study(arguments.getString(0), arguments.getInteger(0));
      		System.out.println(study.getName());
      }
      
      
      //예시3
      
      static class StudyAggregator implements ArgumentsAggregator{
      		   
           @Override
           public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) throws ArgumentsAggregationException {
                 	Study study = new Study(arguments.getString(0), arguments.getInteger(0));
                   
                   return study;
           }
       }
       
       @ParameterizedTest
       @CsvSource({"홍길동" , "10" ,"홍길동2" , "20"})
      void parameterizedTest((@AggregateWith(StudyAggregator.class) Study study){
      
      		System.out.println(study.getName());
      }

      4)@EnumSource

    • 열거형 값의 배열을 테스트에 전달한다.

    • 하나의 인수만을 전달할 수 있다.

    • 사용하고자 하는 Enum클래스를 넘겨주면 된다.

      //예제1
      @ParameterizedTest
      @EnumSource(Enum.class)
      void getValueEnum(Enum enum){
      		System.out.println(enum.getValue());
      }
      
      //예제2
       @EnumSource(value=Enum.class , name={"EnumType1","EnumType2" })
       
      name으로 특정 Enum타입값만 가져와 테스트할 수 있다.

      5)@MethodSource

    • 복잡한 Object를 전달하여 사용할 때 유용하다.

      
      
       @ParameterizedTest
       @MethodSource("studySetUpMethod")
       void studyTest(Study study){
          System.out.println(study.getUsername());
      }
      
      	
      private static Stream<Arguments> studySetUpMethod(){
           return Stream.of(
                   Arguments.of(new Study("홍길동1",10 ,"홍길동아이디1")),
                   Arguments.of(new Study("홍길동2",20 ,"홍길동아이디2"))
           );
      }
      //static 메소드로 만들어줘야 한다.
      //만일 메소드가 외부소스에 있다면 , 패키지명부터 적어줘야 한다

테스트 클래스란

  • 최상위 클래스, 스태틱 멤버 클래스, @Nested 클래스적어도 한개의 @Test 어노테이션이 달린 테스트 메소드가 포함되있는 걸 말한다

  • 테스트 클래스는 abstract 이면 안되고 , 하나의 생성자가 있어야 한다.

테스트 메소드란

- @Test ,@RepeatedTest ,@ParamterizedTest,@TestFactory ,@TestTemplate 같은 메타 어노테이션이 붙은 메소드

라이프 사이클 메소드란

  • @BeforeAll , @AfterAll , @BeforeEach , @AfterEach 같은 메타 어노테이션이 메소드에 붙여진 메소드

주의
테스트 메소드와 라이프사이클 메소드는 abstract 선언하면 안되고, 어떠한 값도 리턴되선 안된다.

Junit5 Assertions

assertEquals & assertNotEquals

  • 두 값을 비교하여 일치 여부 판단한다.
Assertions.assertEquals(3 , add(1,2));

Assertions.assertEquals(4 , add(1,2));

assertArrayEquals

  • 두 배열을 비교하여 일치 여부 판단한다.
	 @Test
    public void arrayTest(){
        int [] arr1 = {1,2,3};
        int [] arr2 = {1,2,3};
       
        Assertions.assertArrayEquals(arr1, arr2 , "일치하지 않습니다.");
        //실패할 경우 메세지 지정
        }

두 배열이 모두 null이어도 동일하다고 처리.

assertNotNull & assertNull

  • 객체의 null 여부 확인
    String str = null;
    String str2 = "data";

assertNull(str);

assertNull(str2,"객체는 null이 아닙니다");

Assertions.assert(str2 , () -> "객체는 null이 아닙니다");
//Supplier 사용
}

assertNotSame & assertSame

  • 객체의 참조가 같은지 확인
String str1 = new String("스트링");
String str2 = new String("스트링");

Assertions.assertNotSame(str1 , str2);
//참

assertTrue & assertFalse

  • 조건이 true인지 false인지.
assertTrue(5 > 3);  

assertTrue(5 < 3 , "5는 3보다 작습니다");
//실패메세지 지정

assertTrue(() -> 3 <5 , "5는 3보다 작습니다");
//BooleanSupplier 사용

fail

  • AssertionFailedError를 발생시킨다.
@Test
void testFail(){
	 throws new IllegalArgumentException("잘못된 인자");

	fail("IllegalArgumentException가 발생하지 않음")
}
  • 만일 IllegalArgumentException이 발생하지 않을 경우 fail로 테스트를 실패시켜 준다.

assertAll

  • 모든 단언들이 한번에 실행되고 , 실패가 함께 되도록 그룹화 한다.
  • 메소드 내에서 인자로 람다식을 사용

    @Test
    public void arrayTest(){
        assertAll(
                "heading",
                () -> assertEquals(1,3),
                () -> assertEquals(1,2),
                () -> assertEquals(1,4)
        );
    }
    
    //결과
    heading (3 failures)
	org.opentest4j.AssertionFailedError: expected: <1> but was: <3>
	org.opentest4j.AssertionFailedError: expected: <1> but was: <2>
	org.opentest4j.AssertionFailedError: expected: <1> but was: <4>

assertThrows

  • 특정 예외가 발생했는지 확인
   @Test
    void testAssertThrow(){
        IllegalArgumentException exceptionInfo = assertThrows(IllegalArgumentException.class,
                () -> {
                    throw new IllegalArgumentException("예외 발생");
                });

        assertEquals("예외 발생" , exceptionInfo.getMessage());
    }
    
    //결과를 꺼내올 수 있다.

assertTimeout & assertTimeoutPreemptively

  • 특정 시간 안에 실행이 완료 되는지
  • 시간 내 실행이 끝나는지 여부 확인 시 : assertTimeout
  • 지정한 시간 내 끝나지 않으면 바로 종료 : assertTimeoutPreemptively

    @Test
    void testTimeout(){
        assertAll("timeOut",
                () -> assertTimeout(ofSeconds(2),() -> Thread.sleep(1000)),
                () -> assertTimeout(ofSeconds(2),() -> Thread.sleep(1000)),

                () -> assertTimeoutPreemptively(ofSeconds(2),() -> Thread.sleep(1999)),
                () -> assertTimeoutPreemptively(ofSeconds(2),() -> Thread.sleep(1999))
//2초 미만으로 수행되어야 종료되지 않음.
                );
    }

assertIterableEquals

  • Iterable의 요소의 수와 순서가 동일해야 하고 반복되는 요소가 동일해야 한다.
   @Test
    void assertIterableEquals(){


        Iterable<Integer> listOne = new ArrayList<>(Arrays.asList(1,2,3,4));
        Iterable<Integer> listTwo = new ArrayList<>(Arrays.asList(1,2,3,4));
        Iterable<Integer> listThree = new ArrayList<>(Arrays.asList(1,2,3));
        Iterable<Integer> listFour = new ArrayList<>(Arrays.asList(1,2,4,3));
        Iterable<Integer> listFive = new LinkedList<>(Arrays.asList(1,2,3,4));

        
 //Test will pass    
 Assertions.assertIterableEquals(listOne, listTwo);
 
 //Test will pass(구현체가 다르더라도 통과)
 Assertions.assertIterableEquals(listOne,listFive);

 //Test will fail (갯수가 다르다고 실패)
 Assertions.assertIterableEquals(listOne, listThree);

 //Test will fail (순서가 다르다고 실패)
 Assertions.assertIterableEquals(listOne, listFour);
    }

assertLinesMatch

  • 목록이 일치하는지 확인
  • 목록을 한쌍씩 비교해가며 넘어간다.
  • String.matches()메서드로 검사 한다.
    (즉 문자열만 가능)
List<String> expected = asList("Java", "\\d+", "JUnit");
        List<String> actual = asList("Java", "11", "JUnit");

        assertLinesMatch(expected, actual);
profile
성실하게

0개의 댓글