본문 바로가기

Web/spring

[Spring Junit Test] 2.5. Assertions

728x90

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

 

JUnit 5 User Guide

Although the JUnit Jupiter programming model and extension model do not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and custo

junit.org

 

 

틀린 해석이 있다면 알려주세요 🐰


All JUnit Jupiter assertions are static methods in the org.junit.jupiter.api.Assertions class.

 

 

import static java.time.Duration.ofMillis;
import static java.time.Duration.ofMinutes;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTimeout;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.concurrent.CountDownLatch;

import example.domain.Person;
import example.util.Calculator;

import org.junit.jupiter.api.Test;

class AssertionsDemo {

    private final Calculator calculator = new Calculator();

    private final Person person = new Person("Jane", "Doe");

    @Test
    void standardAssertions() {
        assertEquals(2, calculator.add(1, 1));
        assertEquals(4, calculator.multiply(2, 2),
                "The optional failure message is now the last parameter");
        assertTrue('a' < 'b', () -> "Assertion messages can be lazily evaluated -- "
                + "to avoid constructing complex messages unnecessarily.");
    }

    @Test
    void groupedAssertions() {
        // In a grouped assertion all assertions are executed, and all
        // failures will be reported together.
        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"))
                );
            }
        );
    }

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

    @Test
    void timeoutNotExceeded() {
        // The following assertion succeeds.
        assertTimeout(ofMinutes(2), () -> {
            // Perform task that takes less than 2 minutes.
        });
    }

    @Test
    void timeoutNotExceededWithResult() {
        // The following assertion succeeds, and returns the supplied object.
        String actualResult = assertTimeout(ofMinutes(2), () -> {
            return "a result";
        });
        assertEquals("a result", actualResult);
    }

    @Test
    void timeoutNotExceededWithMethod() {
        // The following assertion invokes a method reference and returns an object.
        String actualGreeting = assertTimeout(ofMinutes(2), AssertionsDemo::greeting);
        assertEquals("Hello, World!", actualGreeting);
    }

    @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();
        });
    }

    private static String greeting() {
        return "Hello, World!";
    }

}

 

 

assertTimeoutPreemptively() 메소드 주의점

 

이 메소드는 제공된 executable 또는 supplier를 호출하는 스레드와 다른 스레드에서 실행되기 때문에, 해당 메소드 내에서 실행되는 코드가 java.lang.ThreadLocal 스토리지를 사용한다면 예상치 못한 부작용이 발생할 수 있다.

Spring Framework의 트랜잭션 테스팅 지원이 이러한 문제의 일례로 들어가며, Spring의 테스팅 지원은 테스트 메소드가 호출되기 전에 트랜잭션 상태를 현재 스레드에 바인딩하고, 이를 ThreadLocal을 통해 구현한다.

따라서 assertTimeoutPreemptively() 메소드에서 제공된 executable 또는 supplier가 트랜잭션을 사용하는 Spring 관리 컴포넌트를 호출하면, 해당 컴포넌트에서 수행된 작업은 테스트 관리 트랜잭션과 롤백되지 않을 수 있다.

이 경우에는 해당 작업이 영구 저장소 (예: 관계형 데이터베이스)에 커밋되기 때문에 예기치 않은 결과가 발생할 수 있다.

또한, ThreadLocal 저장소를 사용하는 다른 프레임워크에서도 이와 유사한 부작용이 발생할 수 있다.

따라서 assertTimeoutPreemptively() 메소드를 사용할 때는 ThreadLocal 저장소를 사용하는 코드가 있는지 확인하고,

해당 코드에서 예상치 못한 부작용이 발생하지 않도록 주의해야 한다.

728x90