본문 바로가기

개인적으로 공부한 것을 정리해 놓은 블로그입니다 틀린 것이 있으면 댓글 부탁 드립니다!


JAVA

JAVA 스터디 6 - JUNIT5

반응형

과제 0. JUnit 5 학습하세요.

  • 인텔리J, 이클립스, VS Code에서 JUnit 5로 테스트 코드 작성하는 방법에 익숙해 질 것.

  • 이미 JUnit 알고 계신분들은 다른 것 아무거나!

  • 더 자바, 테스트 강의도 있으니 참고하세요~

 

 

과제 1. live-study 대시 보드를 만드는 코드를 작성하세요.(진행중)

  • 깃헙 이슈 1번부터 18번까지 댓글을 순회하며 댓글을 남긴 사용자를 체크 할 것.

  • 참여율을 계산하세요. 총 18회에 중에 몇 %를 참여했는지 소숫점 두자리가지 보여줄 것.

  • Github 자바 라이브러리를 사용하면 편리합니다.

  • 깃헙 API를 익명으로 호출하는데 제한이 있기 때문에 본인의 깃헙 프로젝트에 이슈를 만들고 테스트를 하시면 더 자주 테스트할 수 있습니다.

 

JUNIT 

 자바의 단위테스트 도구이다. 외부 테스트 프로그램을 작성하여 System.out으로 번거롭게 디버깅 하지 않아도 되며 , 

프로그램 테스트 시 걸릴 시간도 관리할 수 있게 해주는 오픈소스이다. 어느정도 개발이 진행되면 프로그램에 대한 단위 테스트는 반드시 수행해야한다. Junit은 보이지 않고 숨겨진 단위 테스트를 끌어내 정형화시켜 단위테스트를 쉽게 해준다.

 

 단위테스트(Unit Test)

 - 소스 코드의 특정 모듈이 의도된 대로 정확히 작동하는지 검증하는 절차

 - 모든 함수와 메소드에 대한 테스트 케이스를 작성하는 절차를 말한다. 

 

JUnit의 특징 

-@Test 메서드가 호출 될때 마다 새로운 인스턴스를 생성하여 독립적인 테스트가 이루어지게 한다.

-assert메서드로 테스트 케이스의 수행 결과를 판별할 수있다. 

-JUnit4 부터는 어노테이션으로 간결하게 테스트를 지원한다. (@Test @Befor @After)

-테스트의 결과가 성공일경우 (녹색) , 실패시(붉은색) 중 하나로 표시된다.

-테스트 결과를 확인하는 것 이외 최적화된 코드를 유추해내는 기능도 제공한다.

 

 

JUNIT 5

JUnit 5 -> Junit Platform + JUnit Jupter + Junit Vintage

 

JUnit  Platform : 테스트를 발견하고 테스트 계획을 생성하는 TestEngine 인터페이스를 가지고 있습니다. Platform은 TestEngine을 통해서 테스트를 발경하고 ,실행하고 ,결과를 보고한다.

 

JUnit Jupiter :TestEngine의 실제 구현체는 별도의 모듈이며 . 모듈 중 하나가 jupiter-engine이다. 이 모듈은 jupiter-API를 사용하여 작성한 테스트 코드를 발견하고 실행한다 .

 Jupiter API는 JUnit 5에 새롭게 추가된 테스트 코드용 API로 , 개발자는 Jupiter API를 사용해서 테스트 코드를 작성할 수 있다.

 

JUnit Vintage : 기존에 JUnit 4 버전으로 작성한 테스트 코드를 실행할 때에는 vintage-engine 모듈을 사용한다. 

 

Junit5는 런타임시 Java 8버전 이상을 필요로한다 . 하지만 이전 버전의 JDK로 컴파일된 코드는 테스트 가능하다.

 

 아래부터는 

junit.org/junit5/docs/current/user-guide/ 의 내용을 번역 하였습니다. 구글 번역기와 제 머리를 함께 사용하기 때문에 틀리거나 어색한 부분이 있을 수 있습니다.  

사용법

maven dependency  

<dependency>
     <groupId>org.junit.jupiter</groupId>
         <artifactId>junit-jupiter-params</artifactId>
         <version>5.7.0</version>
         <scope>test</scope>
</dependency>

 

Gradle

testCompile("org.junit.jupiter:junit-jupiter-params:5.7.0")

 

위 dependency를 추가하여 사용하면 된다.

1. 테스트 작성

아래의 예제는 JUnit Jupiter에서 테스트를 작성하기 위한 최소 요구 사항을 간략하게 보여준다

이 장의 이후 섹션에서는 사용 가능한 모든 기능에 대한 자세한 내용을 제공하고 있다.

public class Calculator {
    public int add(int a , int b){
        return a+b;
    }
    public int minus(int a, int b){
        return a-b;
    }
    public int multiple(int a, int b){
        return a*b;
    }
    public int division(int a , int b){
        return a/b;
    }
}

테스트코드 작성을 위한 간단한 계산기 클래스이다. 

 

package JUnit5;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class CalculatorTest {

    private final Calculator calculator = new Calculator();

    @Test
    void addition(){
        Assertions.assertEquals(2,calculator.add(1,1));
    }
    
}

내가 만든 Calculator 클래스를 검증하는 Test 이다. Test code는 java폴더가 아닌 test 폴더에 저장해놔야한다.

@Test 어노테이션은 해당 메서드는 Test 코드라는 것을 지정한다,

assert메서드는 Assertions 클래스에 static메서드로 구현되 있다. 

테스트 방식은 간단하다 . assert메서드를 활용해 예상하는 값을 지정하고 실제 테스트하는 메서드의 값을 비교하여 테스트한다. 

위의 Test는 calculator의 add 메서드를 검증하고 있다. 

예상값으로 2를 주었고 , 다음 인자로는 add 메서드를 사용하여 리턴되는 값을 지정하였다. 

해당 테스트를 실행하면 아래와 같이 초록색 불이 뜬다.

실패할 경우 아래와 같이 예상값과 실제값,실패 이유 , error 메시지 등이 표시 된다. 

테스트 코드 저장위치

 

 2.어노테이션

 JUnit 테스트 구성 및 확장을 위한 어노테이션 들을 정리해 보았다. 해당 어노테이션은 junit-jupiter-api 모듈에 org.junit.jupiter.api패키지에 위치한다. 

어노테이션 설명
@Test 메서드가 테스트 메서드임을 나타낸다. JUnit4의 @Test 어노테이션과 달리 JUnit Jupiter의  테스트 확장이 자체 전용 어노테이션 기반으로 작동하기 때문에 속성을 선언하지 않아도 된다. 
@ParameteriedTest 매개 변수화 된 테스트임을 나타낸다 . 해당 어노테이션을 포함한 메서드는 재정의 되지 않는 한 상속된다. JUnit 5.7.0버전 이상부터 사용가능하다
여러개의 argument를 이용해 테스트를 한번에 여러개의 테스트를 돌릴 수 있으며 , @ValuesSource (이외에도 여러가지 타입의 source를 사용할 수 있다.
여러가지 타입 source 어노테이션 (lannstark.tistory.com/52 )을 참조하자.) 같은 source 어노테이션을 이용해 값을 지정해 줄 수 있다.
@RepeatedTest 동일한 테스트를 반복해서 수행해야할 경우 사용하는 어노테이션입니다. 성능적인 이슈를 확인하거나 하는 반복적인 테스트를 수행할 경우에 사용할 수 있다.
@RepeatedTest(10) 과 같이 반복 횟수를 정하여 테스트를 반복 실행 시킬 수 있다.
@TestFactory 동적 테스트를 위한  test factory 임을 나타내는 어노테이션. 일반적으로 JUnit의 단위 테스트는 @Test 어노테이션을 작성한 테스트 메소드에 기술하는 것으로 적용되지만. 이를 사용하지 않고 동적으로 테스트를 작성할 수 있게 해주는 어노테이션이다 . 
@TestTemplate @Test 메소드와 달리 테스트 템플릿은 그자체카 테스트 케이스가 아니라 테스트 케이스의 템플릿이다. 따라서 등록된 공급자가 반환하는 호출 컨텍스트 수에 따라 여러 번 호출되도록 설계되었으며 하나 이상의 공급자와 함께 사용해야 한다. 
@TestMethodOrder 테스트 메서드 실행 순서를 구성하는데 사용된다 . JUnit4의 @FixMethodOrder와 유사하다. 일반적으로 테스트는 순서에 의존하지 않도록 작성하는 것이 유지보수 측면에서 바람직하다. 하지만 시퀀셜한 업무흐름을 순서대로 테스트하는 것이 테스트 코드 작성에 편할 경우에 사용할 수 있다.
@TestInstance 해당 어노테이션이 붙은 테스트 인스턴스의 라이프사이클을 설정하는데에 사용된다. 
@DisplayName 테스트 클래스 혹은 테스트 메서드의 이름을 지정하는데에 사용된다.
@DisplayNameGeneration  JUnit5에서 새로 추가된 어노테이션이며 카멜케이스,언더스코어제거등의 클래스를 상속받아  만든 Generator를 지정하여 test class name을 genaration할 수 있다.
@DisplayName 보다 우선순위가 낮다. 
@BeforeEach JUnit4의 @Before와 같은 역할을하며 @BeforeEach가 달린 메서드는
클래스에 위치한 모든 @Test @RepeatedTest @ParameterizedTest @TestFactory 테스트 이전에 실행된다.
@AfterEach JUnit4의 @After와 같은 역할을하며 @AfterEach가 달린 메서드는
클래스에 위치한 모든 @Test @RepeatedTest @ParameterizedTest @TestFactory 테스트 이후에 실행된다.
@BeforeAll 클래스 내의 모든 메서드 실행 전에 한번 실행되며 @BeforeAll 메서드는 static으로 선언되 있어야 한다.  
@AfterAll 클래스 내의 모든 메서드 실행 후에 한번 실행되며 @AfterAll 메서드는 static으로 선언되 있어야 한다.  
@Nested 클래스 내의 중첩클래스를 선언할 때 사용하는 어노테이션이며 @Nested가 달린 클래스는 @BeforeAll , @AfterAll의 사용이 불가능 하다 .(중첩 클래스에서 static선언이 불가한 것 처럼)
@Tag 클래스 혹은 메서드 레벨에서 필터링을 위해 사용되는 Tag를 지정하는 어노테이션이다
CI 빌드 테스트 자동 실행시에 필터링을 통해 특정 태그 이름으로  테스트를 개별적으로 실행할 수 있다.
@Disabled 테스트 클래스 혹은 메서드를 비활성화 시키기 위해 사용한다, Junit4의 @Ignore와 같다
@Timeout 주어진 시간을 초과하는 경우 테스트 , 테스트 팩토리 ,테스트 템플릿 또는 라이프사이클 메서드를 실패하는데 사용된다. 
@ExtendWith 단위 테스트간에 공통적으로 사용할 기능을 구현하여 @ExtendWith를 통하여 적용할 수 있는 기능을 제공합니다. 확장기능은 org.junit.jupiter.api.extension.Extension 인터페이스를 상속한 인터페이스로 되어있으며 JUnit5에서 제공하는 기능의 상당수가 이 기능을 통해서 지원되고 있다
@RegisterExtension @ExtendWith 어노테이션을 통해서 확장기능을 선언적으로 등록할 수 있다라고 한다면 @RegisterExtension을 통해서는 절차적 즉, 프로그램 코드를 이용하여 확장기능을 등록할 수 있습니다. @ExtendWith로 충분하다고 생각되므로 크게 유용성을 못느낄수도 있지만 있겠지만 어노테이션 기반으로 사용하는 경우와는 다르게 생성자(Constructor)를 통해 확장기능에 의존성을 주입하거나, 빌더등을 통해서 프로그램을 통한 설정이 가능해집니다.
@TempDir 라이프 사이클 메서드 또는 테스트 메서드에서 필드주입 또는 매개 변수 주입을 통해 임시 디렉토리를 제공하는 데 사용된다. org.junit.jupiter.api.io 패키지에 위치해 있다.

 

3.Aseertions

JUnit jupiter는 JUnit4가 가지고 있는 많은 assertion 메소드와 함께 제공되며 java 8 람다와 함께 사용하기에 적합한 몇 가지가 추가 되었다. 모든 JUnit Jupiter의 assertion은 org.junit.jupiter.api.Assertions의 스태틱 메서드이다.

 

public class AssertionTest {

    private final Calculator calculator = new Calculator();
    private final Person person = new Person("ugo","hwang");

    @Test
    void standardAssertions(){
        assertEquals(2,calculator.add(1,1));
        //두번째 인자로 실패 메세지를 지정할 수 있다.
        assertEquals(4,calculator.multiple(2,2),"틀렸다");
        assertTrue('a'<'b',()->"틀렸다");
    }
    
    //그룹 Assertion 테스트
    @Test
    void groupedAssertions(){
        //그룹안에 하나만 실패해도 해당 메서드는 실패하게 되고
        //그룹화 된 Assertion에서는 모든 assertion이 실행되며 모든 실패는 함께 보고된다.
        assertAll("person",
                    ()->assertEquals("ugo",person.getFirstname()),
                    ()->assertEquals("hwag",person.getLastname())
                );
    }
    //코드 블록 내에 어설 션이 실패하면 동일한 블록의 후속코드는 건너뛴다.
    @Test
    void dependentAssertions(){
        assertAll("properties",
                () -> {

                    String firstName = person.getFirstname();
                    //null인지 테스트하는 Assertion
                    //여기서 Test가 실패하면 아래의 테스트는 모두 실행되지 않는다.
                    assertNotNull(firstName);
                    //여러개의 테스트를 한번에 구현하며 assertAll 안에 테스트중 하나만 실패해도 모두 실패한다.
                    assertAll("first name",
                            () -> assertTrue(firstName.startsWith("u")),
                            () -> assertTrue(firstName.endsWith("o"))
                    );
                },
                ()-> {
                    String lastName = person.getLastname();
                    assertNotNull(lastName);

                    assertAll("last name",
                            () -> assertTrue(lastName.startsWith("h")),
                            () -> assertTrue(lastName.endsWith("g"))
                    );
                });
    }

    @Test
    void exceptionTesting(){
        //발생할 예외에 대해서 test하고 예상하는 예외가 맞다면 Exceoption 변수에 담긴다
        Exception exception = assertThrows(ArithmeticException.class,
                ()->calculator.division(1,0));
        //위의 exception에서 message를 가져와 비교하였다.
        assertEquals("/ by zero",exception.getMessage());
    }

    @Test
    void timeoutNouExceeded(){
        //테스트 수행 시간을 지정한다 첫번째 인자로는 수행시간이고 , 두번째 인자로는 수행할 테스트이다. 
        assertTimeout(ofSeconds(1),()->{
            //2분안에 실행될 수 있는 것은 통과하고 그렇지 못하다면 테스트에 실패한다.
            Thread.sleep(2000);
        });
    }
    @Test
    void timeoutNotExceededWithResult() {
        //테스트 실행후 리턴값을 넘겨줄 수도 있다.
        //아래의 경우 만약 테스트가 2분아래라면  a result라는 문자열을 넘겨주게 되어있다.
        String actualResult = assertTimeout(ofMinutes(2), () -> {
            System.out.println("테스트");
            return "a result";
        });
        assertEquals("a result", actualResult);
    }
    //클래스 내의 static method를 불러와서 사용할 수도 있다.
    @Test
    void timeoutNotExceededWithMethod() {
        // 2번째 인자의 메서드가 지정한 시간내에 실행 되었을 경우 테스트에 성공하고 해당 메서드의 리턴값을 리턴해준다.
        String actualGreeting = assertTimeout(ofMinutes(2), AssertionTest::greeting);
        assertEquals("Hello, World!", actualGreeting);
    }
    private static String greeting() {
        return "Hello, World!";
    }
}

 

 

 

 

 

 

참조

junit.org/junit5/docs/current/user-guide/

 

devuna.tistory.com/39  

 

beomseok95.tistory.com/303#:~:text=JUnit%20Jupiter%EB%8A%94%20%EB%AC%B8%EC%9E%90%EC%97%B4%EC%97%90%EC%84%9C,%ED%8F%B4%EB%B0%B1%20%EB%A9%94%EC%BB%A4%EB%8B%88%EC%A6%98%EC%9D%84%20%EC%A0%9C%EA%B3%B5%ED%95%A9%EB%8B%88%EB%8B%A4.  

 

reiphiel.tistory.com/entry/junit5-features2

반응형