JUnit
JUnit은 무엇인가
슈슈슉민
2023. 10. 17. 23:18
왜 사용할까
기능을 구현하면 그 기능이 잘 되는지 test를 해야한다. 그렇게 하기 위해서는 구현 부분과 테스크 코드가 필요하다. 한 클래스내에 이것들을 놓게 되면 문제점이 생기게 된다.
1. 하나의 이름안에 여러개의 기능이 생길 텐데 복잡도가 증가하고 메소드 이름이 모호해진다.
2. 테스트결과를 하드코딩해야한다.
3. test code도 같이 배포 된다.
이런 문제점들을 모두 해결해주기 위해 JUnit이 등장했다.
@Test | 메서드가 테스트 메서드임을 나타냅니다. Junit 4의 @Test 주석과는 달리 Junit 목성의 테스트 확장은 자신의 전용 annotation을 기반으로 작동하므로 이 annotation 은 속성을 선언하지 않습니다. 이러한 메서드는 override되지 않는 한 상속됩니다. |
@ParameterizedTest | 메서드가 매개 변수화된 테스트임을 나타냅니다. 이러한 메서드는 재정의되지 않는 한 상속됩니다. |
@RepeatedTest | 메서드가 반복 테스트에 대한 테스트 템플릿임을 나타냅니다. 이러한 메서드는 재정의되지 않는 한 상속됩니다. |
@TestFactory | 메서드가 동적 테스트를 위한 테스트 팩토리임을 나타냅니다. 이러한 메서드는 재정의되지 않는 한 상속됩니다. |
@TestTemplate | 메소드가 등록된 공급자가 반환하는 호출 컨텍스트의 수에 따라 여러 번 호출되도록 설계된 테스트 케이스의 템플릿임을 나타냅니다. 이러한 메소드는 override 되지 않는 한 상속됩니다. |
@TestClassOrder | 주석이 달린 테스트 클래스에서 @Nested 테스트 클래스에 대한 테스트 클래스 실행 순서를 구성하는 데 사용됩니다. 이러한 annotation 은 상속됩니다. |
@TestMethodOrder | Junit 4의 @FixMethodOrder와 유사하게 주석이 달린 테스트 클래스의 테스트 메서드 실행 순서를 구성하는 데 사용됩니다. 이러한 annotation 은 상속됩니다. |
@TestInstance | 주석이 달린 테스트 클래스의 테스트 인스턴스 라이프사이클을 구성하는 데 사용되며, 이러한 annotation 은 상속됩니다. |
@DisplayName | 테스트 클래스 또는 테스트 메서드에 대한 사용자 지정 표시 이름을 선언합니다. 이러한 annotation 은 상속되지 않습니다. |
@DisplayNameGeneration | 테스트 클래스에 대한 사용자 지정 표시 이름 생성기를 선언합니다. 이러한 annotation 은 상속됩니다. |
@BeforeEach | 현재 클래스의 각 @Test, @RepeatedTest, @ParameterizedTest 또는 @TestFactory 메서드보다 먼저 annotation 이 달린 메서드를 실행해야 함을 나타냅니다. Junit 4의 @Before와 유사합니다. 이러한 메서드는 재정의되거나 대체되지 않는 한 상속됩니다(즉, Java의 가시성 규칙과 관계없이 서명만 기반으로 대체됨). |
@AfterEach | 현재 클래스의 각 @Test, @RepeatedTest, @ParameterizedTest 또는 @TestFactory 메서드 다음에 주석이 달린 메서드를 실행해야 함을 나타냅니다. Junit 4의 @After와 유사합니다. 이러한 메서드는 재정의되거나 대체되지 않는 한 상속됩니다(즉, Java의 가시성 규칙과 관계없이 서명만 기반으로 대체됨). |
@BeforeAll | 현재 클래스의 모든 @Test, @RepeatedTest, @ParameterizedTest 및 @TestFactory 메서드보다 먼저 주석이 달린 메서드를 실행해야 함을 나타냅니다. Junit 4의 @BeforeClass와 유사합니다. 이러한 메서드는 숨김, 무시 또는 대체되지 않는 한(즉, Java의 가시성 규칙과 관계없이 서명만 기반으로 대체) 상속되며 "클래스별" 테스트 인스턴스 라이프사이클을 사용하지 않는 한 정적이어야 합니다. |
@AfterAll | 현재 클래스의 모든 @Test, @RepeatedTest, @ParameterizedTest 및 @TestFactory 메서드를 사용한 후 주석이 달린 메서드를 실행해야 함을 나타냅니다. Junit 4의 @AfterClass와 유사합니다. 이러한 메서드는 숨김, 무시 또는 대체되지 않는 한(즉, Java의 가시성 규칙과 관계없이 서명만 기반으로 대체) 상속되며 "클래스별" 테스트 인스턴스 라이프사이클을 사용하지 않는 한 정적이어야 합니다. |
@Nested | 주석이 달린 클래스가 정적이 아닌 중첩 테스트 클래스임을 나타냅니다. Java 8에서 Java 15까지는 "클래스 단위" 테스트 인스턴스 라이프사이클을 사용하지 않는 한 @BeforeAll 및 @AfterAll 메서드를 @Nested 테스트 클래스에서 직접 사용할 수 없습니다. Java 16부터는 @BeforeAll 및 @AfterAll 메서드를 테스트 인스턴스 라이프사이클 모드의 @Nested 테스트 클래스에서 정적으로 선언할 수 있습니다. 이러한 주석은 상속되지 않습니다. |
@Tag | 클래스 또는 메서드 수준에서 필터링 테스트를 위한 태그를 선언하는 데 사용되며 테스트 NG의 테스트 그룹 또는 Junit 4의 범주와 유사합니다. 이러한 주석은 클래스 수준에서는 상속되지만 메서드 수준에서는 상속되지 않습니다. |
@Disabled | Junit 4의 @Ignore와 유사한 테스트 클래스 또는 테스트 메서드를 비활성화하는 데 사용됩니다. 이러한 주석은 상속되지 않습니다. |
@Timeout | 테스트, 테스트 팩토리, 테스트 템플릿 또는 라이프사이클 방법의 실행이 지정된 기간을 초과할 경우 실패하는 데 사용되며, 이러한 주석은 상속됩니다. |
@ExtendWith |
확장명을 선언적으로 등록하는 데 사용됩니다. 이러한 주석은 상속됩니다.
|
@RegisterExtension | 필드를 통해 프로그래밍적으로 확장명을 등록하는 데 사용됩니다. 이러한 필드는 음영 처리되지 않는 한 상속됩니다. |
@TempDir | 라이프사이클 방법 또는 테스트 방법으로 필드 주입 또는 매개 변수 주입을 통해 임시 디렉토리를 제공하는 데 사용되며, org.junit.jupiter.api.io 패키지에 있습니다. |
공식문서 2.3. Test Classes and Methods
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
class StandardTests {
@BeforeAll
static void initAll() {
}
@BeforeEach
void init() {
}
@Test
void succeedingTest() {
}
@Test
void failingTest() {
fail("a failing test");
}
@Test
@Disabled("for demonstration purposes")
void skippedTest() {
// not executed
}
@Test
void abortedTest() {
assumeTrue("abc".contains("Z"));
fail("test should have been aborted");
}
@AfterEach
void tearDown() {
}
@AfterAll
static void tearDownAll() {
}
}
공식문서 2.5. Assertions
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.Tag;
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!";
}
}
이부분은 신기해서 적어봤다..ㅋㅋㅋ
2.8.1. Operating System and Architecture Conditions
@Test
@EnabledOnOs(MAC)
void onlyOnMacOs() {
// ...
}
@TestOnMac
void testOnMac() {
// ...
}
@Test
@EnabledOnOs({ LINUX, MAC })
void onLinuxOrMac() {
// ...
}
@Test
@DisabledOnOs(WINDOWS)
void notOnWindows() {
// ...
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Test
@EnabledOnOs(MAC)
@interface TestOnMac {
}
Conditional execution based on architecture
@Test
@EnabledOnOs(architectures = "aarch64")
void onAarch64() {
// ...
}
@Test
@DisabledOnOs(architectures = "x86_64")
void notOnX86_64() {
// ...
}
@Test
@EnabledOnOs(value = MAC, architectures = "aarch64")
void onNewMacs() {
// ...
}
@Test
@DisabledOnOs(value = MAC, architectures = "aarch64")
void notOnNewMacs() {
// ...
}
2.8.2. Java Runtime Environment Conditions
@Test
@EnabledOnJre(JAVA_8)
void onlyOnJava8() {
// ...
}
@Test
@EnabledOnJre({ JAVA_9, JAVA_10 })
void onJava9Or10() {
// ...
}
@Test
@EnabledForJreRange(min = JAVA_9, max = JAVA_11)
void fromJava9to11() {
// ...
}
@Test
@EnabledForJreRange(min = JAVA_9)
void fromJava9toCurrentJavaFeatureNumber() {
// ...
}
@Test
@EnabledForJreRange(max = JAVA_11)
void fromJava8To11() {
// ...
}
@Test
@DisabledOnJre(JAVA_9)
void notOnJava9() {
// ...
}
@Test
@DisabledForJreRange(min = JAVA_9, max = JAVA_11)
void notFromJava9to11() {
// ...
}
@Test
@DisabledForJreRange(min = JAVA_9)
void notFromJava9toCurrentJavaFeatureNumber() {
// ...
}
@Test
@DisabledForJreRange(max = JAVA_11)
void notFromJava8to11() {
// ...
}
출처: https://junit.org/junit5/docs/current/user-guide/#overview-what-is-junit-5
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