[Java] JUnit5 μ¬μ©λ² μμ보기
JUnitμ΄λ?
π² Java κ°λ°μμ 93%κ° μ¬μ©νλ λ¨μ ν μ€νΈ νλ μμν¬
π² μ€νλ§ λΆνΈ 2.2λ²μ μ΄μλΆν° κΈ°λ³Έ μ 곡νλ€.
JUnit5λ?
π² Platform : ν μ€νΈλ₯Ό μ€ννκ² ν΄μ£Όλ λ°μ²λ₯Ό μ 곡νλ€. TestEngine APIλ₯Ό μ 곡.
π² Jupiter : JUnit5λ₯Ό μ§μνλ TestEngine API ꡬν체
π² Vintage : JUnit4μ 3λ₯Ό μ§μνλ TestEngine ꡬν체
JUnit5λ₯Ό μμνλ λ°©λ²
π² μ€νλ§λΆνΈ 2.2λ²μ μ΄μλΆν°λ κΈ°λ³Έ νμ¬λλ€.
π² κ·Έ μΈμλ maven, νΉμ gradle λ°©μμΌλ‘ μΆκ°ν μ μλ€.
gradle
test {
useJUnitPlatform()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
}
maven
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
Annotations
πΆ @Test
ν μ€νΈ λ©μλμμ λνλ΄λ μ΄λ Έν μ΄μ μ΄λ€. JUnit4μλ λ€λ₯΄κ² μ΄λ ν μμ±λ κΈ°μ¬νμ§ μλλ€.
@Test
void create() {
...
}
πΆ μλͺ μ£ΌκΈ°(LifeCycle) μ΄λ Έν μ΄μ
π² @BeforeAll : ν΄λΉ ν΄λμ€μ μμΉν λͺ¨λ ν μ€νΈ λ©μλ μ€ν μ , ν λ²λ§ μ€νλλ λ©μλ
π² @AfterAll : ν΄λΉ ν΄λμ€μ μμΉν λͺ¨λ ν μ€νΈ λ©μλ μ€ν ν, ν λ²λ§ μ€νλλ€.
π² @BeforeEach : ν΄λΉ ν΄λμ€μ μμΉν λͺ¨λ ν μ€νΈ λ©μλ μ€ν μ μ μ€νλλ λ©μλ, BeforeAllκ³Ό λ¬λ¦¬ λͺ¨λ λ©μλ λ§λ€ μ€νλλ€.
π² @AfterEach : ν΄λΉ ν΄λμ€μ μμΉν λͺ¨λ ν μ€νΈ λ©μλ μ€ν νμ μ€νλλ λ©μλ, AfterAllκ³Ό λ¬λ¦¬ λͺ¨λ λ©μλλ§λ€ μ€νλλ€.
πΆ @DisplayName
μ΄λ€ ν μ€νΈμΈμ§ μ½κ² ννν μ μλλ‘ ν΄μ£Όλ μ΄λ Έν μ΄μ μ΄λ€. 곡백μ΄λ, Emoji, νΉμλ¬Έμ₯ λ± λͺ¨λ μ§μνλ€.
@DisplayName("ν
μ€νΈμ
λλ€")
void test() {
...
}
πΆ RepeatedTest
νΉμ ν μ€νΈλ₯Ό λ°λ³΅μν€κ³ μ ν λ μ¬μ©νλ€. λ°λ³΅ νμλ _value_ μμ±μ ν΅ν΄ μ€μ νκ³ , _name_μμ±μ ν΅ν΄ κ·Έ μ΄λ¦λ μ§μ΄μ€ μ μλ€.
@RepeatedTest(value = 10, name = "")
@DisplayName("λ°λ³΅")
void repeatedTest() {
...
}
πΆ @ParameterizedTest
ν μ€νΈμ μ¬λ¬ λ€λ₯Έ λ§€κ°λ³μλ₯Ό λμ ν΄κ°λ©° λ°λ³΅ μ€νν λ μ¬μ©νλ€. μμ£Ό μ¬μ©λλ μ΄λ Έν μ΄μ μ΄λΌ κΈ°μ΅ν΄λ λ§νλ€.
π² CsvSource
@ParameterizedTest
@CsvSource(value = {"ACE,ACE:12", "ACE,ACE,ACE:13"}, delimiter = ":")
@DisplayName("")
void useParameterizedTest(final String input, final int expected) {
}
key: valueλλμΌλ‘ inputκ³Ό expectμ :λ₯Ό κΈ°μ€μΌλ‘ splitλ κ°λ€μ΄ λ€μ΄μ¨λ€.
π² ValueSource
@ParameterizedTest
@ValueSource(strings = {"", " "})
void isBlankTest(String input) {
assertTrue(Strings.isBlank(input));
}
test λ©μλ μ€ν μμ input κ°μΌλ‘ νλμ μΈμκ° μ λ¬λλ€. μ΄λ, 리ν°λ΄ κ°μ λ°°μ΄μ ν μ€νΈ λ©μλλ‘ μ λ¬νλ€.
π² Null and Empty Values
_@NullSource_λ₯Ό μ¬μ©νλ©΄ μΈμλ‘ nullκ°μ μ λ¬νκ³ , _@EmptySource_λ₯Ό μ¬μ©νλ©΄ μΈμλ‘ λΉ κ°μ μ λ¬νλ€. λ§μ½ _@NullAndEmptySource_λ₯Ό μ¬μ©νλ©΄ nullκ°κ³Ό λΉ κ° λͺ¨λλ₯Ό μ λ¬ν μ μλ€.
νΉμ΄ν κ²μ _@EmptySource_μ _@NullAndEmptySource_λ λ¬Έμμ΄, 컬λ μ , λ°°μ΄μμλ λμνλ€.
@ParameterizedTest
@NullAndEmptySource
void isBlankTest(String input) {
asertTrue(Strings.isBlank(input));
}
π² Enum
_@EnumSource_λ₯Ό ν΅ν΄ μ΄κ±°ν κ°μ λ°°μ΄μ ν μ€νΈ λ©μλλ‘ μ λ¬ν μ μλ€. μ΄λ, νλμ μΈμλ§μ μ λ¬νλ€.
μμ±μΌλ‘λ _name_μ΄ μλλ°, μ κ· ννμμ μ λ¬ν μ μμΌλ©°, _mode_λΌλ μμ±μΌλ‘ μ μΈν enumμ μ§μ ν μλ μλ€.(EXCLUDE)
@ParameterizedTest
@EnumSource(
value = Month.class,
names = {"APRIL", "JUNE", "SEPTEMBER", "NOVEMBER", "FEBRUARY"},
mode = EnumSource.Mode.EXCLUDE)
void exceptFourMonthsTest(Month month) {
final boolean isALeapYear = false;
assertEquals(31, month.length(isALeapYear));
}
πΆ @Nested
ν μ€νΈ ν΄λμ€ μμμ λ΄λΆ ν΄λμ€λ₯Ό μ μνμ¬ ν μ€νΈλ₯Ό κ³μΈ΅νν λ μ¬μ©νλ€.
Assertions
ν μ€νΈ μΌμ΄μ€μ μν κ²°κ³Όλ₯Ό νλ³νλ λ©μλμ΄λ€. λν λͺ¨λ JUnit Jupiter Assertionsλ static λ©μλμ΄λ€.
import static org.assertj.core.api.Assertions.*;
Object Assertion
public class Dog {
private String name;
private Float weight;
}
Dog fido = new Dog("Fido", 5.25);
Dog fidosClone = new Dog("Fido", 5.25);
μ£Όμ΄μ§ μ½λμ λν΄μ μλμ κ°μ΄ ν μ€νΈν μ μλ€.
assertThat(fido).isEqualTo(fidosClone);
// λ΄μ© μ체λ₯Ό λΉκ΅νκΈ° μν΄μλ μλ μ¬μ©
assertThat(fido).isEqualToComparingFieldByFieldRecursively(fidosClone);
Boolean Assertion
assertThat("".isEmpty()).isTrue();
Iterable/Array Assertion
πΆ Iterable/Arrayκ° νΉμ μμλ₯Ό μ§λλ μ§ ν μ€νΈ
List<String> list = Arrays.asList("1", "2", "3");
assertThat(list).contains("1");
πΆλΉ κ°μΈ μ§ νμΈ
assertThat(list).isNotEmpty();
πΆ 첫 λ²μ§Έ μμκ° νμΈ
assertThat(list).startsWith("1");
πΆ 체μ΄λμ ν΅ν μ¬μ΄ ν μ€νΈ
assertThat(list)
.isNotEmpty()
.contains("1")
.doesNotContainNull()
.containsSequence("2", "3");
Character Assertion
assertThat(someCharacter)
.isNotEqualTo('a')
.inUnicode()
.isGreaterThanOrEqualTo('b')
.isLowerCase();
Map Assertion
key/valueλ entryλ₯Ό μ§λ Map μλ£κ΅¬μ‘°μ λν΄μλ ν μ€νΈκ° κ°λ₯νλ€.
assertThat(map)
.isNotEmpty()
.containsKey(2)
.doesNotContainKeys(10)
.contains(entry(2, "a"));
πΆ assertAll(executables...)
λ§€κ° λ³μλ‘ λ°λ λͺ¨λ ν μ€νΈμ½λλ₯Ό ν λ²μ μ€ννλ€. κ·Έλμ μ€λ₯κ° λλ λκΉμ§ μ€νν νμ ν λ²μ λͺ¨μμ μΆλ ₯νλ€.
assertAll(
() -> assertNotNull(study),
() -> assertEquals(Status.STARTED, study.getStatus(), "μ²μ μνκ° DRAFT"),
() -> assertTrue(study.getLimit() > 0, "μ΅λ μΈμμ 0λ³΄λ€ μ»€μΌνλ€.")
);
μ΄μΈμ Assertion
πΆ assertThrows(extectedType, excutable)
μμΈ λ°μμ νμΈνλ ν μ€νΈμ΄λ€. λ‘μ§μ΄ μ€νλλ λμ€ μμΈλ₯Ό λ°νλ°μμ, μμΈμ μνκΉμ§ μ μ μλ€.
@Test
void exceptionThrow() {
Exception e = assertThrows(Exception.class, () -> new Test(-10));
assertDoesNotThrow(() -> System.out.println("Do Something"));
}
체μ΄λμ ν΅ν΄μ μλμ κ°μ΄ ν΄λμ€μ μ’ λ₯λ λ©μΈμ§λ μμλΌ μ μλ€.
// when, then
assertThatThrownBy(() -> TryCountValidator.validate(input))
.isInstanceOf(NullPointerException.class)
.hasMessage("μ
λ ₯κ°μ΄ 곡백μ΄λ©΄ μλ©λλ€.");
πΆ assertTimeout(duration, excutable)
νΉμ μκ° μμ μ€νμ΄ μλ£λλμ§ νμΈνλ€. _Duration_μ μνλ μκ°μ΄λ©°, _Executable_μ ν μ€νΈν λ‘μ§μ΄λ€.
@Test
@DisplayName("νμμμ μ€μ")
void timeoutNotExceeded() {
assertTimeout(ofMinutes(2), () -> Thread.sleep(10));
}
πΆ Assumption
μ μ λ¬Έμ΄ trueλΌλ©΄ μ€ννκ³ , falseλΌλ©΄ μ’ λ£νλ€. CIμ κ°μ΄ νΉμ νκ²½μμλ§ ν μ€νΈλ₯Ό μννμ¬μΌ ν λ μ¬μ©ν μ μλ€.
π² assumeTrue : falseμΌ λ, ν μ€νΈ μ μ²΄κ° μ€νλμ§ μλλ€.
π² assumingThat : νλΌλ―Έν°λ‘ μ λ¬λ μ½λλΈλλ§ μ€νλμ§ μλλ€.
void some_test() {
assumeTrue("DEV".equals(System.getenv("ENV")), () -> "κ°λ° νκ²½μ΄ μλλλ€.");
assertEquals("A", "A")' // μΆλ ₯ x
}
References
https://gmlwjd9405.github.io/2019/11/27/junit5-guide-parameterized-test.html