Lee's Grow up

[TDD/테스트주도개발] JUnit5 본문

Agile/TDD(Test Driven Development)

[TDD/테스트주도개발] JUnit5

효기로그 2020. 1. 30. 10:11
반응형

시작하며

해당 내용은 JUnit 5 User Guide와 인프런의 더 자바, 애플리케이션을 테스트하는 다양한 방법을 정리한 내용입니다. 잘못된 부분은 댓글로 피드백 부탁드립니다.

개요

이전 버전의 JUnit와는 다르게 JUnit5 부터는 모듈의 집합으로 구성되어 있음, 또한 JUnit5는 java8 이상의 버전을 필요로하나, 그 하위 버전도 JDK로 컴파일된 코드는 계속 테스트 가능
JUnit 5 = Junit Platform + Junit Jupiter + JUnit Vintage

  • Junit Platform
    • TestEngine API를 제공해주며, 콘솔 실행 런처를 제공해줍니다.
  • JUnit Jupiter
    • TestEngine API 구현체로 JUnit5 API를 제공
  • JUnit Vintage
    • 하위 버전과 호환을 위해 JUnit3, JUnit4 기반의 테스트 엔진을 제공

어노테이션

JUnit5 어노테이션내용JUnit4 어노테이션
@Test테스트 Method임을 선언@Test
@ParameterizedTest매개변수를 받는 테스트를 작성할 수 있다. 
@RepeatedTest반복되는 테스트 작성 가능 
@TestFactory@Test로 선언된 정적 테스트가 아닌 동적으로 테스트를 사용 
@TestInstance테스트 클래스의 생명주기를 설정 
@TestTemplate공급자에 의해 여러 번 호출될 수 있도록 설계된 테스트 케이스 템플릿임을 나타낸다. 
@TestMethodOrder테스트 메소드 실행 순서를 구성하는데 사용 
@DisplayName테스트 클래스 또는 메소드의 사용자 정의 이름을 선언할 때 사용 
@DisplayNameGeneration

이름 생성기를 선언, 예를 들어 '_'를 공백 문자로 치환해주는 생성기가 있다. ex ) new_test -> new test

 
@BeforeEach모든 테스트 실행 전에 실행할 테스트에 사용@Before
@AfterEach모든 테스트 실행 후에 실행한 테스트에사용@After
@BeforeAll현재 클래스를 실행하기 전 제일 먼저 실행할 테스트 작성 static로 선언@BeforeClass
@AfterAll현재 클래스 종료 후 해당 테스트를 실행 static로 선언@AfterClass
@Nested클래스를 정적이 아닌 중첩 테스트 클래스임을 나타냅니다. 
@Tag클래스또는 메소드 레벨에서 태그를 선언할 때 사용, 이를 메이븐을 사용할 경우 설정에서 테스트를 태그를 인식해 포함하거나 제외시킬 수 있다 
@Disabled이 클래스나 테스트를 사용하지 않음을 표시@Ignore
@Timeout테스트 실행 시간을 선언 후 초과되면 실패하도록 설정 
@ExtendWith확장을 선언적으로 등록할 때 사용 
@RegisterExtension필드를 통해 프로그래밍 방식으로 확장을 등록할 때 사용 
@TempDir필드 주입 또는 매개변수 주입을 통해 임시 디렉토리를 제공하는데 사용 

각각 어노테이션의 사용법은 추후 시간이 나면 포스팅하고, 참고할 만한 링크를 남깁니다. 위에 링크로 연결된 JUnit5 User Guide 또는 아래 링크에서 사용법 설명을 볼 수 있습니다.
baeldung : https://www.baeldung.com/?s=Guide+JUnit5

사용자 정의 어노테이션

JUnit Jupiter의 주석은 메타 주석 으로 사용할 수 있습니다.
예를 들어 @Tag("fast")를 매번 사용하는 대신 @FAST로 정의하여 사용 가능

  • Fast Annotation
    @Target({ ElementType.TYPE, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Tag("fast")
    public @interface Fast { }  
  • Test Method
    @Fast
    @Test
    void myFastTest() { 
    ...
    }

    나아가 @Fast, @Test를 섞은 @FastTest로도 선언가능

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Tag("fast")
    @Test
    public @interface FastTest {
    }

테스트 클래스 및 메소드 작성

Test Class : 최소한 하나의 테스트 메소드를 포함하는 최상위 클래스를 말하며, abstract이면 안되고, single constructor이어야 한다.
Test Method : @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @TestTemplate로 선언된 테스트 메소드
Lifecycle Method : @BeforeAll, @AfterAll, @BeforeEach, @AfterEach 로 선언된 메소드

  • 작성 예시
    class MyTest{
    
    @BeforeAll
    static void initAll(){ }
    
    @BeforeEach
    void init(){ }
    
    @Test
    void standardTest() { }
    
    @Test
    @Disabled("description 작성, 데모용 테스트")
    void disabledTest(){ }
    
    @Test
    void abortedTest() {
       assumeTrue("abc".contains("Z"));
       fail("test should have been aborted");
    }
    
    @AfterEach
    void tearDown() { } 
    
    @AfterAll
    static void testDownAll() { }
    }

    위와 같이 간단하게 테스트를 작성했다고 가정하면 실행 순서는
    initAll() -> init() -> standardTest() -> tearDown() -> init() -> abortedTest() -> tearDown() -> testDownAll() 순으로 동작하게 된다.

JUnit5 Assertions

기본적으로 JUnit4의 Assertions를 포함하며 java 8 람다와 함께 사용하기 위해 몇가지를 추가했습니다. 모든 JUnit Jupiter 어셜션은 클래스의 static 메소드입니다. org.junit.jupiter.api.Assertions

메소드설명
assertAll ( executables...)

구문 오류시 예외를 발생시키지 않으면서 한번에 모든 구문을 확인 할 수 있습니다. 

assertEquals( expected, actual )실제 값과 예상 값이 같은지 확인, 이 외 assertArrayEquals(), assertNotEquals() 도 존재
assertNotNull( actual )값이 null인지 아닌지 확인
assertTrue( boolean )다음 조건이 참인지 확인
assertThrows( expectedType,  executable )예외를 발생 시키는지 확인
assertTimeout ( timeout, executable )특정 시간안에 실행하는지 확인
assertTimeoutPreemptively( timeout, executable ) 특정 시간안에 실행하는지 확인 후, 시간을 초과하면 실행 파일의 실행이 중단되도록 설정. 단, executable와 다른 스레드에서 실행하기 때문에, 원치 않는 결과가 발생할 수 있다. ex ) 트랜잭션이 적용이 안되서 롤백이 안되는 경우

이 외 assertSame()등, 많은 메소드가 존재하니 자세한 내용은 아래 API 링크를 통해 참고하시면 될 것 같습니다.

JUnit5 Assertions API : https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/Assertions.html

그 외 써드파티 라이브러리로 AssertJ, Hamcrest, Truth등을 사용할 수 있습니다.

작성 예시
  • 객체 생성 테스트

    @Test
    void create_new_member{
      Member member = Member.builder()
                      .name("Lee")
                      .build();
      assertNotNull(member);
      assertEquals("Lee", member.getName());
    }

    위와 같이 객체 생성이 정상적으로 되었는지, 값이 원하는 값인지 확인하는 테스트를 작성할 수 있습니다.
    또한 assert에는 마지막 인자로 String 또는 Supplier<String>를 통해 메세지를 출력할 수 있습니다.

  • 테스트 결과 오류메세지 설정

    @Test
    void create_new_member{
      Member member = Member.builder()
                      .name("Lee")
                      .build();
      // assertEquals("Park", member.getName(), "생성된 member의 name이 Lee가 아닙니다.");
      assertEquals("Park", member.getName(), () -> "생성된 member의 name이 Lee가 아닙니다.");
    }
  • Exception 확인
    Member에 age라는 필드를 추가해주고 생성자를 통해 생성 시 음수가 들어오면 Exception을 발생하도록 로직을 만들어 줍니다.

    public Class Member {
      private int age;
    
      public Member(int age) {
          if( age < 0 ) throw new IllegalArgumentException();
          this.age = age;
      }
    }

    위와 같이 Member 클래스가 있을 경우 아래와 같이 테스트 작성 가능

    @Test
    void create_new_member{
      assertThrows(IllegalArgumentException.class, () -> new Member(-100));
    }

    나아가 Exception 타입으로 리턴값을 받을 경우 해당 exception을 활용가능

    @Test
    void create_new_member{
      IllegalArgumentException exception = 
          assertThrows(IllegalArgumentException.class, () -> new Member(-100));
    
      // excetpion 활용..
    }
  • assertTimeout
    시간에 대한 제한을 줄 경우 사용

    void create_new_member{
      assertTimeout(Duration.ofSeconds(2), () -> {
          new  Member();
          Thread.sleep(3000);
      });
    }

    해당 assertion의 경우 시간제한을 2초로 주었지만, 실제 실행인 3초 이상까지 테스트가 실패하는 경우에도 실행을 하게 됩니다. 이럴 경우 사용하는 assertion이 assertTimeoutPreemptively이며 사용법은 동일합니다.
    다만 ThreadLocal을 사용하는 경우 예상치 못한 예외를 발생할 수 있기 때문에 사용에 주의해야 합니다.

반응형

'Agile > TDD(Test Driven Development)' 카테고리의 다른 글

[TDD/테스트주도개발] TDD 개념  (0) 2020.01.03
Comments