공부/Junit

JUnit5 Assertions, Assumptions

leejinwoo1126 2023. 8. 11. 15:31
반응형

 


목차


    JUnit4 Assert, Assume

    Assert, Assume 의 경우 JUnit 5에서 아래와 같이 대체가 된 것으로 확인된다.

    JUnit4 JUnit5
    org.junit.Assert org.junit.jupiter.api.Assertions
    org.junit.Assume org.junit.jupiter.api.Assumptions

     

    개인적으로 버전업에 따른 API 추가 외에 보이는 차이점은 아래와 같다.

    - JUnit4 Assert 에서는 Hamcrest Matcher 파라미터로 지원하였으나, JUnit5에서는 지원하지 않음 

    - 마찬가지로 JUnit4 Assume에서도 Hamcrest Matcher 지원하였으나, JUnit5에서는 지원하지 않고 함수형 인터페이스(BooleanSupplier) 지원하면서, 람다식 인자 전달 가능해짐

     

     

    *JUnit5 API를 먼저 살펴보고 JUnit4 API가 궁금할 경우 아래 Doc 참고

    - Assert API Doc

    - Assume API Doc


    JUnit5 Assertions

    패키지 org.junit.jupiter.api.Assertions
    정의 Assertions is a collection of utility methods that support asserting conditions in tests.

     

    - 아래의 경우 공식 문서의 예제 코드를 사용함

    - 자세한 API 명세의 경우 JUnit5 Assertions API Doc  참고 

    - spring-boot-start-test 의존성 추가하면 테스트 라이브러리 포함되어 있음 (spring boot 2.7.0, maven 참고)

    dependencies {
        //..
        testImplementation 'org.springframework.boot:spring-boot-starter-test'
        //..
    }

     

     

    1) 기본 - assertEquals, assertNotEquals

    두 값이 일치하는지 확인

    @Test
    void standardAssertions() {
        double result = Calculator.PLUS.apply(1, 2);
    
        assertEquals(3, result); // ok
    }

     

    두 값이 다른지 확인 (같을 경우 메시지 출력)

    @Test
    void assertNotEquals() {
        int value = 0;
        
        // AssertionFailedError: the result cannot be 0 
        Assertions.assertNotEquals(0, value, "the result cannot be 0");
    }

     

    2) 그룹 - assertAll

    - 그룹화된 Assertion 테스트를 실행

    - Executable 인자로 람다식 사용하여 여러개의 테스트 실행

    - 실행 후 실패 건에 대해 MultipleFailuresError 생성자에  heading 정보와 함께 전달하여 콘솔 출력

    // Assertions.class
    public static void assertAll(Executable... executables) throws MultipleFailuresError {
        AssertAll.assertAll(executables);
    }
    
    public static void assertAll(String heading, Executable... executables) throws MultipleFailuresError {
        AssertAll.assertAll(heading, executables);
    }

     

    - Executable 의 경우 @FunctionalInterface 이기 때문에 람다식으로 표현 가능

    @FunctionalInterface
    @API(status = STABLE, since = "5.0")
    public interface Executable {
    
    	void execute() throws Throwable;
    
    }

     

    예시

    private final Person person = new Person("Jane", "Doe");
    
    @Test
    void groupedAssertions() {
        assertAll("person",
                () -> assertEquals("Jane", person.getFirstName()),
                () -> assertEquals("Doe", person.getLastName())
        );
    }
    
    @Test
        void dependentAssertions() {
            // Within a code block, if an assertion fails the
            // subsequent code in the same block will be skipped.
            assertAll("properties",
                    () -> {
                        String firstName = person.getFirstName();
                        assertNotNull(firstName);
                        
                        // Executed only if the previous assertion is valid.
                        assertAll("first name",
                                () -> assertTrue(firstName.startsWith("J")),
                                () -> assertTrue(firstName.endsWith("e"))
                        );
                    },
                    () -> {
                        // Grouped assertion, so processed independently
                        // of results of first name assertions.
                        String lastName = person.getLastName();
                        assertNotNull(lastName);
                        
                        // Executed only if the previous assertion is valid.
                        assertAll("last name",
                                () -> assertTrue(lastName.startsWith("D")),
                                () -> assertTrue(lastName.endsWith("e"))
                        );
                    }
            );
        }

     

    3) 예외 확인 - assertThrows

    - 메서드 실행 했을 때 특정 예외가 발생하였는지 확인

    - 첫번째 인자(expectedType) : 확인할 예외 클래스 타입 토큰 

    - 두번째 인자(executable) : 실행하려는 메서드(람다식)

    // Assertions.class
    public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable) {
        return AssertThrows.assertThrows(expectedType, executable);
    }

     

    예시

    @Test
    void exceptionTesting() {
        Exception exception = assertThrows(ArithmeticException.class,
                () -> Calculator.DIVIDE.apply(1, 0));
        assertEquals("/ by zero", exception.getMessage());
    }

     

    4) 시간 초과 - assertTimeout, assertTimeoutPreemptively

    - assertTimeout : 특정시간 안에 테스트가 끝나는지 확인, 시간 초과 하더라도 실행 메서드가 끝난 후 결과 리턴

    - assertTimeoutPreemptively : 특정시간 안에 테스트가 끝나지 않을 경우 바로 강제 종료

    @Test
    void timeoutExceeded() {
        // The following assertion fails with an error message similar to:
        // execution exceeded timeout of 10 ms by 91 ms
        assertTimeout(ofMillis(10), () -> {
            // Simulate task that takes more than 10 ms.
            Thread.sleep(100);
        });
    }
    
    @Test
    void timeoutExceededWithPreemptiveTermination() {
        // The following assertion fails with an error message similar to:
        // execution timed out after 10 ms
        assertTimeoutPreemptively(ofMillis(10), () -> {
            // Simulate task that takes more than 10 ms.
            new CountDownLatch(1).await();
        });
    }

     

    시간 제한을 10ms 두고, 100ms 씩 Thread를 정지 시켰을 때 총 127ms 실행 시간이 소요되는 것을 확인가능했다

    총 127ms = 100ms (assertTimeout) + 10ms(assertTimeoutPreemptively)  + @ (자원해제)
    @Test
    void timeoutExceedTest() {
        assertAll(
                () -> assertTimeout(Duration.ofMillis(10), () -> Thread.sleep(100)),
                () -> assertTimeoutPreemptively(Duration.ofMillis(10), () -> Thread.sleep(100))
        );
    }

     

     

     

    Baeldung 예시 참고

    5) assertArrayEquals

    - 예상 배열과 실제 배열이 동일한지 확인 

    - 같지 않을 경우 세번째 인자 메시지 출력

    @DisplayName("배열의 값이 같은지 확인한다")
    @Test
    void assertArrayEquals() {
        char[] expected = {'J', 'u', 'p', 'i', 't', 'e', 'r'};
        char[] actual = "Jupiter".toCharArray();
    
        Assertions.assertArrayEquals(expected, actual, "Arrays should be equal");
    }

     

    6) assertTrue , assertFalse

    - assertTrue의 경우 조건이 true이면 테스트 통과, false이면 메시지 출력

    - assertFalse의 경우 조건이 false이면 테스트 통과,  true인 경우 메시지 출력

    @DisplayName("조건이 참인가")
    @Test
    void assertTrue() {
        Assertions.assertTrue(5 > 4, "5 is greater than 4");
        Assertions.assertTrue(null == null, "null is equal to null");
    
        BooleanSupplier condition = () -> 10 > 1;
        Assertions.assertTrue(condition);
    }
    
    @DisplayName("조건이 거짓인가")
    @Test
    void assertFalse() {
        BooleanSupplier condition = () -> 5 > 6;
        Assertions.assertFalse(condition, "5 is not greater than 6");
    }

     

    7) assertNull, assertNotNull

    - 객체의 null 여부 확인

    - assertNull : 객체가 null 이면 true

    - assertNotNull : 객체가 not null 이면 true

    @DisplayName("")
    @Test
    void assertNotNull() {
        Object dog = new Object();
    
        Assertions.assertNotNull(dog, () -> "The dog should not be null");
    }
    
    @DisplayName("")
    @Test
    void assertNull() {
        Object cat = null;
    
        Assertions.assertNull(cat, () -> "The cat should be null");
    }

     

    8) assertSame, assertNotSame

    동일한 Object 참조 주소를 가지는지 판별

    @DisplayName("")
    @Test
    void assertSame() {
        String language = "Java";
        Optional<String> optional = Optional.of(language);
    
        Assertions.assertSame(language, optional.get()); // ok
    }

     

    9) assertIterableEquals

    - Iterable 인터페이스를 구현한 클래스에 대해 deeply equals(내용, 값) 비교

    - 동일한 순서로 내용만 일치하는지 판별 

    - 아래와 같이 타입은 달라도 상관없음

    @Test
    void assertIterableEquals() {
        Iterable<String> al = new ArrayList<>(Arrays.asList("Java", "Junit", "Test"));
        Iterable<String> ll = new LinkedList<>(Arrays.asList("Java", "Junit", "Test"));
    
        Assertions.assertIterableEquals(al, ll);
    }

     

    10) assertLinesMatch

    - 예상과 결과 List 인스턴스가 같은지 확인

    - String.macthes() 로 인덱스 별로 두 인자가 동일한지 판별 (패턴 검사 수행)

    - fast-forward marker 확인(이해 못함)

    @Test
    void assertLinesMatch() {
        List<String> expected = Arrays.asList("Java", "\\d+", "JUnit");
        List<String> actual = Arrays.asList("Java", "11", "JUnit");
    
        Assertions.assertLinesMatch(expected, actual);
    }
    두번째 "\\d+" 의 경우 digit 숫자에 + (하나 이상) 을 뜻하는 정규 표현식이라서 "11"과 비교시 통과 

     

    11) fail

    - 해당 테스트는 fail로 끝나고 실패 메시지를 콘솔 출력

    - 개발이 완료 되지 않은 테스트를 표시하는 경우 유용

    @Test
    void fail() {
        // Test not completed
        Assertions.fail("FAIL - test not completed");
    }

     


    JUnit5 Assumptions

    패키지 org.junit.jupiter.api.Assumptions
    정의 Assumptions is a collection of utility methods that support conditional test execution based on assumptions.

    - Assumption : 추정, 가정

     

    공식 문서 예제 코드

    Assumptions API 의 경우 특정 환경인 경우를 가정하여 테스트 진행하도록 합니다

    @DisplayName("true인 경우 테스트 통과, false 인 경우 test ignore 된다")
    @Test
    void testOnlyOnCiServer() {
        assumeTrue("CI".equals(System.getenv("ENV")));
    }
    
    @DisplayName("true인 경우 테스트 통과, false 인 경우 test ignore 된다")
    @Test
    void testOnlyOnDeveloperWorkstation() {
        assumeTrue("DEV".equals(System.getenv("ENV")),
                () -> "Aborting test: not on developer workstation");
    }
    
    @DisplayName("true인 경우 Executable 실행하고, false 인 경우 해당 테스트를 건너 다음 테스트를 실행한다")
    @Test
    void testInAllEnvironments() {
        assumingThat("CI".equals(System.getenv("ENV")),
                () -> {
                    // perform these assertions only on the CI server
                    assertEquals(2, Calculator.DIVIDE.apply(4, 2));
                });
    
        assertEquals(42, Calculator.MULTIPLY.apply(6, 7));
    }

     

    - 환경 변수(ENV=CI) 설정 후 테스트 실행시 두번째 테스트만 Ignore 되고, 나머지는 통과한다


    참고

    Assertions in JUnit4 and JUnit5

     

    [JUnit5] 테스트에 timeout 걸기

     

    JUnit5 필수 개념 정리(JAVA)

     

    https://junit.org/junit5/docs/current/user-guide/#writing-tests-assertions

     

    https://junit.org/junit5/docs/current/user-guide/#writing-tests-assumptions

    반응형