JUnit5 개념 잡기

2017. 6. 2. 17:35Java

JUnit5에 대한 트윗이 많아지고 있고, 가이드 등이 하나 둘씩 나오고 있습니다.
처음부터 끝까지 이해하기 위해서는 junit.org에서 제공하는 User Guide를 읽어 보는 것이 가장 나이스 할것 같습니다.

하지만... 전체적으로 맛보기를 하는 것이 좋을 듯 싶어서,
아래 Guide를 정리해 놓은 사이트에 있는 내용을 우선 번역을 해본뒤에..
이후 시간을 다시 내어, User Guide를 번역해 보는 것이 좋을 것 같습니다.

2. a Guide to JUnit5 : http://www.baeldung.com/junit-5


# JUnit5을 위한 maven 설정

저는 IDE을 Intellij을 사용합니다.
maven 프로젝트를 선택해서, groupid 와 artifactID을 지정해서 프로젝트를 생성합니다.
아래는 http://junit.org/junit5/docs/current/user-guide/ 에서 제시한 pom 설정을 그대로 차용했습니다. 두둥.

# pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>junit5</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <junit.version>4.12</junit.version>
        <junit.jupiter.version>5.0.0-M4</junit.jupiter.version>
        <junit.vintage.version>${junit.version}.0-M4</junit.vintage.version>
        <junit.platform.version>1.0.0-M4</junit.platform.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.19.1</version>
                <configuration>
                    <includes>
                        <include>**/Test*.java</include>
                        <include>**/*Test.java</include>
                        <include>**/*Tests.java</include>
                        <include>**/*TestCase.java</include>
                    </includes>
                    <properties>
                        <!-- <includeTags>fast</includeTags> -->
                        <excludeTags>slow</excludeTags>
                    </properties>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.junit.platform</groupId>
                        <artifactId>junit-platform-surefire-provider</artifactId>
                        <version>${junit.platform.version}</version>
                    </dependency>
                    <dependency>
                        <groupId>org.junit.jupiter</groupId>
                        <artifactId>junit-jupiter-engine</artifactId>
                        <version>${junit.jupiter.version}</version>
                    </dependency>
                    <dependency>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                        <version>${junit.vintage.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit.jupiter.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>


그리고 나서 테스트 해봅시다.

#Calculator.class

public class Calculator {

    public int add(int a, int b){
        return a+b;
    }
}


#FirstUnitTest.class

package com.example;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;

import java.util.Arrays;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

@Tag("fast")
class FirstUnitTest {

    @Test
    @DisplayName("My 1st JUnit 5 test!")
    void lambdaExpressions(){
        List<Integer> numbers = Arrays.asList(1,2,3);
        assertTrue(numbers
        .stream()
        .mapToInt(i -> i)
        .sum() > "Sum should be greater than 5");
    }
    @Test
    @DisplayName("My 2nd JUnit 5 test!!")
    void mySecondTest(TestInfo testInfo){
        Calculator calculator = new Calculator();
        assertEquals(2calculator.add(1,1)"1+1 should equal 2");
        assertEquals("My 2nd JUnit 5 test!!"testInfo.getDisplayName(),()->"TestInfo is injected correctly");
    }
}

다음, 실행 해봐서 테스트 잘 되면 성공!!!


# Dependency  Diagram

component diagram



이제부터는 구체적인 내용에 대한 설명에 대해서 따라갑시다. 



1. A Guide to JUnit5

1. Overview

JUnit은 자바 에코시스템에서 단위 테스팅 프레임워크로써 가장 유명한 것중 하나입니다.
현재 안정 버전은 JUnit 4.12 이지만,  JUnit 5.0.0-M4 version은 많은 혁신을 포함하고 있고,
자바 8 이상에서 제공하는 새로운 버전을 지원하는 것을 목표로 하고 있고,
뿐만 아니라 테스팅에 대한 수 많은 다른 스타일들을 가능하게 해줍니다.

2. Maven Dependencies

JUnit5.0.0-M2을 설치하는 것은 매우 쉽답니다. 
pom.xml에 아래 의존관계를 주입하면 됩니다. 

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

여기서 주의할 점은 Java 8 이상에서만 동작한다는 점입니다.
게다가, JUnit Platform 상의 단위 테스트를 Eclipse에서 아직 직접 지원을 아직 하지 않습니다.
물론 IDE안에 Maven Test goal을 사용하여 테스트를 구동할수 있습니다.

3. Architecture

JUnit5는 세개의 하위 프로젝트에 여러 모듈들로 구성되어 있습니다.
(* Unlike previous versions of JUnits, JUnit5 is composed of several different modules from three different sub-projects)

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

3-1) JUnit Platform

JUnit Platform은 JVM 상에 테스팅 프레임워크를 런칭하기 위해서는 필수적입니다.
JUnit Platform은 마치 빌드툴과 같이, JUnit 과 해당 클라이언트 간에  안정적이고 강력한 인터페이스를 정의합니다.
최종목적은 JUnit 을 가지고  클라이언트들이 테스트들을 발견하고 실행하는 법을 쉽게 이해 할수 있게 하는 것입니다.
JUnit Platform은 JUnit 플렛폼상에서 가동되는 테스팅 프레임워크를 개발하는데 필요한 TestEngine API을 정의합니다.
따라서, 3rd 테스팅 라이브러리를 custom TestEngine을 구현하여, 직접 JUnit안에 플러그인 할수 있습니다.

3-2) JUnit Jupiter

이 모델은 JUnit5으로 테스트들을 작성하기 위한 새로운 프로그래밍 과 확장 모델들을 포함하고 있습니다.
JUnit4에 비교된 새로운 어노테이션들은 아래와 같습니다.

  • @TestFactory : 동적 테스드들을 위한 테스트 팩토리인 메서드를 나타냅니다.
  • @DisplayName : 테스트 클래스 나 테스트 메서드을 위한 커스텀  디스플레이 네임을 정의합니다.
  • @Nested : 이 표시된 클래스는 nested, non-static 테스트 클래스이다 라는 것을 나타냅니다.
  • @Tag : 필터링 테스트들을 위한 테그들을 정의합니다.
  • @ExtendWith : custom extensions을 등록할때  사용됩니다.
  • @BeforeEach : 이 표시된 메서드는 각각의 테스트 메서드 전에 실행됩니다. (이전에는 @Before)
  • @AfterEach  : 이 표시된 메서드는 각각의 테스트 메서드 후에 실행됩니다. (이전에는 @After)
  • @BeforeAll : 이 표시된 메서드는 현재 클래스에서 모든 메스트 메서드들 전에  실행됩니다.(이전에는 @BeforeClass)
  • @AfterAll : 이 표시된 메서드는 현재 클래스에서 모든 테스트 메서드들 후에 실행됩니다.(이전에는 @AfterClass)
  • @Disable : 테스트 클래스 나 메서드 를 비활성화 할때 사용됩니다.(이전에는 @ignore)

3-3) JUnit Vintage

JUnit 5 플렛폼 상에서 , JUnit 3 과 JUnit 4 기반의 테스트들이 구동되는 것을 지원합니다.

4. Basic Annotations

새로운 어노테이션에 대해서 논의하기 위해, 우리는 섹션을 아래와 같이 나눴습니다.
실행에 책임을 : 테스트 전, 테스트 동안(선택적), 테스트 후 로 나눴습니다.

4.1 @BeforeAll and @BeforeEach

아래는 메인 테스트 케이스들 전에 실행되는 간단한 코드 예제입니다.

@BeforeAll
static void setip(){   
    log.info("@BeforeAll - executes once before all test methods in this class");
}

@BeforeEachvoid init(){   
    log.info("@BeforeEach - executes before each test method in this class");
}

중요한 점은 @BeforeAll 어노테이션이 있는 메소드는 static이 여야합니다.
그렇지 않으면, 코드는 컴파일하지 않습니다.

4.2. @DisplayName and @Display

새로운 test-optional 메서드들로 이동해 봅시다.

@DisplayName("Single test successful")
@Test
void testSingleSuccessTest(){    
log.info("Success");
}

@Test
@Disabled("Not implemented yet")
void testShowSomething(){}

보다 시피, 새로운 어노테이션을 사용하여, display 이름을 변경할수도 있고, 커맨트를 붙여서 메소드를 비활성화도 가능합니다. 

4.3. @AfterEach and @AfterAll

마지막으로, 테스트 실행후에 동작에 연결되는 메소드들을 알아봅시다.

@AfterEachvoid
tearDown(){
   log.info("@AfterEach - executed  after each test method.");
}

@AfterAll
static void done(){  
    log.info("@AfterAll - executed after all test methods.");
}

마찬가지로 @AfterAll인 메소드는 static 메소드가 되어 합니다.

5. Assertions and Assumptions.

 아래 다시 정리 해 놨습니다. 

6. Exception Testing

JUnit5에서는 예외 테스팅에 대해서 두가지 방법이 있습니다.
두가지 방법 모두 assertThrows() 메소드를 사용해서 구현할수 있습니다.

@Test
void shouldThrowException(){   
              Throwable  exception = assertThrows(
                   UnsupportedOperationException.class, () -> {      
                        throw new UnsupportedOperationException("Not supported");  
                    });  
               assertEquals(exception.getMessage(),"Not supported");
}

@Test
void assertThrowsException(){  
     String str = null;  
     assertThrows(IllegalArgumentException.class, () -> {     
         Integer.valueOf(str);  
     }
}

첫번째 예제는 던져진 예외에 대해서 보다 자세하게 검증하는데 사용되었고,
두번째 예제는 예외에 대한 타입만 체크합니다.

7.Test Suites

JUnit 5에 새로운 기능에 대해 계속 진행하기 위해서, 
하나의 test suite에서 다수의 테스트 클래스들을 모으는 컨셉에 대해서 알아야 할 필요가 있습니다. 
이들을 함께  동작하도록 하기 위해서죠.
JUnit5은  두개의 어노테이션을 제공하는데 : @SelectPackages 와 @SelectClasses :
이것들이 바로 test suites을 생성하는데 사용됩니다.
첫번째 예제를 한번 보시죠

@RunWith(JUnitPlatform.class)
@SelectPackage("com.baeldung")
public class AllTests{}

@SelectPackage은 하나의 test suite를 구동하는데, 선택될 패키지들의 이름을 구분하는데 사용됩니다.
우리 예제에서는, @SelectPackage는 모든 테스트를 구동할 것입니다.
두번째 어노테이션인 @SelectClasses은 하나의 Test Suite를 기곧할때 선택될 클래스들을 구분하는데 사용됩니다.

@RunWith(JUnitPlatform.class)
@SelectClasses({AssertionTest.class, AssumptionTest.class, ExceptionTest.class})
public class AllTests{}

예시로, 위의 클래스는 세개의 테스트 클래스들이 포함된 하나의 suite를 생성할 것입니다.
알아둘 점은, 클래스들은 하나의 단일 패키지 안에 있을 필요가 없습니다.

8. Dynamic Tests

여기서 소개하고자 하는 마지막 토픽은 JUnit Dynamic Tests 기능입니다.
이 기능들은 런타임하에서 생성된 테스트 케이스들을 선언하고 실행할 수 있습니다.
컴파일 타임에서 테스트 케이스의 고정된 수를 정의한 Static Test와는 반대로,
Dynamic Tests은 런타임상에서 동적으로 테스트 케이스를 정의하는 것이 가능하게 해줍니다.
다이나믹 테스트은 @TestFactory 어노테이션된 팩토리 메소드에 의해 생성될수 있습니다.
코드 예제를 보시도록 하겠습니다.

@TestFactory
public stream<DynamicTest> translateDynamicTestsFromStream(){   
return  in.stream().map(word ->
      DynamicTest.dynamicTest("Test translate " + word, () -> {
          int id = in.indexOf(word);
          assertEquals(out.get(id), translate(word));
     }
  });
}

이 예제는 매우 직관적이고 이해하기 쉽습니다.
in 과 out 이라고 이름의 두개의 ArrayList를 각각 사용하여 단어를 바꾸길 원합니다.
factory method는 Stream, Collection, Iterable 또는 Iterator를 리턴해야 합니다.
여기서는 Java 8 Stream 을 선택했습니다.

@TestFactory 메소드는 private 혹은 static이 되면  안됩니다.
테스트들의 개수는 동적이고, ArrayList 사이즈에 따라 달라집니다.

9. Conclusion

이번 기사는 JUnit5가 가져오는 변환에 대해서 짧게 훑어 봤습니다.
우리는 빌드 툴, IDE, 단위 테스트 프레임워크가지고 통합, 플렛폼 런처에 연관된 아키텍쳐안에 큰 변화를 가지고 있는 JUnit 5을 볼수 있었습니다. 더 나아가 JUnit5 는 Java 8과 더 많은 통합되어 있습니다. 특히 람다 와 스트림 개념들 이겠죠.



2. The basics of JUnit 5 - A preview

1. Overview

Junit 은 Java에 대해 가장 유명한 단위 테스팅 프레임워크 중 하나 이기에, 새로운 메이저 버전이 나올때 개발 커뮤니티에서는 빅딜입니다.
JUnit5의 알파버전이 이른 이월에 출시되었고, 흥미로운 변화들을 포함하고 있습니다.
이번 기사에는 이번 출시의 새로운 기능들을 탐험하고, 이전 버전과 다른 메인에 대해 알아보도록 합시다.

2. Dependencies and Setup

JUnit5 은 매우 설치하기가 직관 적입니다.
아래 의존관계를 여러분의 pom.xml에 추가하면 끝입니다.

<dependency>   
   <groupId>org.junit</groupId>
   <artifactId>junit5-api</artifactId>
   <version>5.0.0-ALPHA</version>
</dependency>

하지만 현 시점(2017년 5월)에는 JUnit5을 지원하는 IDE가 아직없습니다.
그래서 아래 빌드 스크립트를 명시해야 합니다.


<plugin>
   <artifactId>maven-surefire-plugin</artifactId>
   <version>2.19</version>
   <dependencies>
       <dependency>
           <groupId>org.junit</groupId>
           <artifactId>surefire-junit5</artifactId>
           <version>5.0.0-SNAPSHOT</version>
       </dependency>
   </dependencies>
</plugin>

그리고 가장 중요한 점은 JUnit5의 작동을 위한 필수 조건으로  Java  8 이상을 설치 하셔야 합니다.

테스트를 생성할때, org.junit.gen5.api.Text 아니면 org.junit.Test 가 확실하게 import 되었는지 확인 바랍니다.
또한 테스트 메소드는 더이상 public 으로 구성할 필요가 없습니다. : package local will do.

3. What's New

JUnit5은 Java 8, 특히 람다 표현식의 새로운 기능들의 전체 장점을 끌어오려 노력했습니다.

3.1. Assertions

Assertions 은 "org.junit.gen5.api.Assertions" 로 옮겨졌고, 의미심장하게 개선되어졌습니다.
이미 언급했지만, 여러분은 assertions 에 람다 표현식을 이제 사용할 수 있습니다.


@Test
void lambdaExpression(){
    List<Integer> number = Arrays.asList(1,2,3);
    assertTure(numbers
                     .stream()
                     .mapToInt(i -> i)
                     .sum() > 5, () -> "Sum should be greater than 5");
}

비록 위의 예제는 별볼일 없지만,  assertion message에 람다 표현식을 사용하는 하나의 장점은 게으르게 평가되므로,
메시지 구조이 비싸면, 시간과 리소스들을 절약할수 있다는 이점이 있습이다.

또한 지금 assertions을 assertAll()을 이용해서 그룹질수 있는 것이 가능한데,
이건 MultipleFailuresError을 가진 그룹안에 실패한 assertion들을 리포트 할것입니다. 


@Test
void groupAssertions(){
    int [] numbers = {0,1,2,3,4};
    assertAll("numbers",
                   () -> assertEquals(numbers[0],1),
                   () -> assertEquals(numbers[3],3),
                   () -> assertEquals(numbers[4],1)
    );
}

이게 의미하는 건 보다 복잡한 assertion들을 만들때 좀 더 안정적입니다.
여러분이 실패한 정확한 위치를 핀포인트(가르킬수) 할수 있을것이다.


3.2 Assumptions

Assumptions은 오직 확실한 상태라고 가정하고 테스트들 가동할때 사용되곤 합니다.
이건 전형적으로 적절하게 동작하는 테스트를 위해 필요로 한 외부 상태로 사용되어 지나,
직접적으로 테스트 하고 있는 어떤것에도 어떤 연관도 되어 있지 않습니다.

여러분은 assumeTrue(), assumeFalse() , assumingThat()을 가지고 assumption을 선언할수 있습니다.

@Test
void trueAssumption(){
    assumeTrue( 5 > 1);
    assertEquals( 5 + 2  , 7);
}

@Test
void falseAssumption(){
     assumeFalse( 5 < 1);
     assertEquals( 5 + 2 , 7);
}

@Test
void assumptionThat(){
     String someString = "Just a string";
     assumingThat(
          someString.equals("Just a string"),
          () -> assertEquals( 2 + 2 , 4)
     );
}

asuumption이 실패하면, TestAbortedException 이 던져지고, 테스트는 간단히 넘어갑니다.
Assumptions은 또한 람다식을 이해합니다.

3.3 Exceptions

JUnit5은 예외에 대한 지원이 증대시켰습니다.
assertThrows() 메소드는 표현식이 주어진 타입의 표현식을 던지는 것을 검증하는 것을 추가하였습니다.

@Test
void shouldThrowException(){
     Throwable exception = assertThrows(UnsupportedOperationException.class() -> {
          Throw new UnsupportedOperationException("Not supperted");
     });
     assertEquals(exception.getMessage(), "Not supported");
}

예시가 설명한대로, JUnit5은 JUnit4가 사용되었던 것보다 던져진 예외들을 넘는 더 많은 컨트롤을 제공합니다. 
가장 분명한 함축적의미는 우리가 예외에 관해 필요할지 모르는 어떠한 정보를 쉽게 얻을수 있는 것이 가능하다는 점입니다.
예외 메세지를 점검한 예제에서 했던것과 같이 말이죠

3.4. Nested Tests

Nested Tests는 테스트들의 다른 그룹들간에 복잡한 관계들을  개발자들에게 표현하는 것을 가능하게  하는 것을 추가해왔습니다.
Syntax은 매우 직관적입니다. - 여러분이 해야할 모든것은 @Nested을 포함한 내부 클래스를 주석을 달아주는 것입니다.
JUnit 문서는 정교하게 만든 예제(http://junit.org/junit5/docs/current/user-guide/#nested-tests)를 제공합니다.
가능한 사용들중에 하나를  설명합니다.

3.5. Disabling Tests

@Disabled 어노테이션으로 테스트를 비활성화 할수 있습니다.

@Test
@Disabled
void disabledTest(){
     assertTrue(false);
}

이 테스트는 동작하지 않습니다.
@Disable 어노테이션은 테스트 케이나 테스트 메소드에 적용될수 있고, JUnit4의 @ignore 와 동일합니다.

3.6 Tagging

Tags은 JUnit4의 Categories에 대신한것입니다.
Tags은 @Tag 어노테이션을 가지고 적용할수 있습니다.
이것들은 개발자들에게 테스트를 그룹하고 필터하는 것을 가능하도록 합니다.

@Tag("Test case")
public class TaggedTest{

     @Test
     @Tag("Method")
     void testMethod(){
          assertEquals(2+2 , 4);

}


4. Conclusion

이번 기사는 JUnit5가 가져오는 변환에 대해서 짧게 훑어 봤습니다.
우리는 빌드 툴, IDE, 단위 테스트 프레임워크가지고 통합, 플렛폼 런처에 연관된 아키텍쳐안에 큰 변화를 가지고 있는 JUnit 5을 볼수 있었습니다. 더 나아가 JUnit5 는 Java 8과 더 많은 통합되어 있습니다. 특히 람다 와 스트림 개념들 이겠죠.


여기까지 정리를 이번주에 진행했고, 
아래 세번재 dynamic test에 대해서는 차주중에 진행 하겠습니다. 
또한, http://junit.org/junit5/docs/current/user-guide/ 에 대해서도 간략하게 나마 훑어 보도록 하겠습니다.
--------------------------------------------------------------------------------------




'Java' 카테고리의 다른 글

core java 따라하기 - 2  (0) 2017.06.15
core java 따라하기 - 1  (0) 2017.06.13
java8 StringJoiner 과 String.join 활용  (0) 2016.01.05
ThreadLocal 이 뭘까요?  (0) 2016.01.05
Apache Daemon 에 대해서  (3) 2015.12.08
Runtime.addShutdownHook()  (0) 2015.09.30
Top 10 Useful, Yet paranoid Java Programming Techniques  (0) 2015.09.15
Exception  (0) 2013.08.04
Regular Expressions in Java  (0) 2013.07.30
Delegate, Event , Ramda  (0) 2013.07.04