JiwonDev

์ž๋ฐ” ํ…Œ์ŠคํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - JUnit5, AssertJ

by JiwonDev

์ž๋ฐ”์˜ ํ…Œ์ŠคํŒ…์„ ์œ„ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ์ด๋‹ค.

JUnit5๋Š” Java8 ์ด์ƒ๋ถ€ํ„ฐ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋ฉฐ, JUnit3,4์™€ ๋‹ค๋ฅธ ์—”์ง„(5 โžก Jupiter, ~4 โžกVintage)์œผ๋กœ ๊ตฌ๋™๋œ๋‹ค.

 

# JUnit5 ์–ด๋…ธํ…Œ์ด์…˜ ๋ ˆํผ๋Ÿฐ์Šค

JUnit5  ๋‚ด์šฉ JUnit4 
@Test ํ…Œ์ŠคํŠธ Method์ž„์„ ์„ ์–ธํ•จ. @Test
@ParameterizedTest ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ฐ›๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Œ.  
@RepeatedTest ๋ฐ˜๋ณต๋˜๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Œ.  
@TestFactory @Test๋กœ ์„ ์–ธ๋œ ์ •์  ํ…Œ์ŠคํŠธ๊ฐ€ ์•„๋‹Œ ๋™์ ์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•จ.  
@TestInstance ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค์˜ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ์„ค์ •ํ•จ.  
@TestTemplate ๊ณต๊ธ‰์ž์— ์˜ํ•ด ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ํ…œํ”Œ๋ฆฟ์ž„์„ ๋‚˜ํƒ€๋ƒ„.  
@TestMethodOrder ํ…Œ์ŠคํŠธ ๋ฉ”์†Œ๋“œ ์‹คํ–‰ ์ˆœ์„œ๋ฅผ ๊ตฌ์„ฑํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•จ.  
@DisplayName ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค ๋˜๋Š” ๋ฉ”์†Œ๋“œ์˜ ์‚ฌ์šฉ์ž ์ •์˜ ์ด๋ฆ„์„ ์„ ์–ธํ•  ๋•Œ ์‚ฌ์šฉํ•จ.  
@DisplayNameGeneration ์ด๋ฆ„ ์ƒ์„ฑ๊ธฐ๋ฅผ ์„ ์–ธํ•จ. ์˜ˆ๋ฅผ ๋“ค์–ด '_'๋ฅผ ๊ณต๋ฐฑ ๋ฌธ์ž๋กœ ์น˜ํ™˜ํ•ด์ฃผ๋Š” ์ƒ์„ฑ๊ธฐ๊ฐ€ ์žˆ์Œ. ex ) new_test -> new test  
@BeforeEach ๋ชจ๋“  ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์ „์— ์‹คํ–‰ํ•  ํ…Œ์ŠคํŠธ์— ์‚ฌ์šฉํ•จ. @Before
@AfterEach ๋ชจ๋“  ํ…Œ์ŠคํŠธ ์‹คํ–‰ ํ›„์— ์‹คํ–‰ํ•œ ํ…Œ์ŠคํŠธ์— ์‚ฌ์šฉํ•จ. @After
@BeforeAll ํ˜„์žฌ ํด๋ž˜์Šค๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์ „ ์ œ์ผ ๋จผ์ € ์‹คํ–‰ํ•  ํ…Œ์ŠคํŠธ ์ž‘์„ฑํ•˜๋Š”๋ฐ,  static๋กœ ์„ ์–ธํ•จ. @BeforeClass
@AfterAll ํ˜„์žฌ ํด๋ž˜์Šค ์ข…๋ฃŒ ํ›„ ํ•ด๋‹น ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋Š”๋ฐ,  static์œผ๋กœ ์„ ์–ธํ•จ. @AfterClass
@Nested ํด๋ž˜์Šค๋ฅผ ์ •์ ์ด ์•„๋‹Œ ์ค‘์ฒฉ ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค์ž„์„ ๋‚˜ํƒ€๋ƒ„.  
@Tag ํด๋ž˜์Šค ๋˜๋Š” ๋ฉ”์†Œ๋“œ ๋ ˆ๋ฒจ์—์„œ ํƒœ๊ทธ๋ฅผ ์„ ์–ธํ•  ๋•Œ ์‚ฌ์šฉํ•จ.  ์ด๋ฅผ ๋ฉ”์ด๋ธ์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ์„ค์ •์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ํƒœ๊ทธ๋ฅผ ์ธ์‹ํ•ด ํฌํ•จํ•˜๊ฑฐ๋‚˜ ์ œ์™ธ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Œ.  
@Disabled ์ด ํด๋ž˜์Šค๋‚˜ ํ…Œ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ์„ ํ‘œ์‹œํ•จ. @Ignore
@Timeout ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์‹œ๊ฐ„์„ ์„ ์–ธ ํ›„ ์ดˆ๊ณผ๋˜๋ฉด ์‹คํŒจํ•˜๋„๋ก ์„ค์ •ํ•จ.  
@ExtendWith ํ™•์žฅ์„ ์„ ์–ธ์ ์œผ๋กœ ๋“ฑ๋กํ•  ๋•Œ ์‚ฌ์šฉํ•จ.  
@RegisterExtension ํ•„๋“œ๋ฅผ ํ†ตํ•ด ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹์œผ๋กœ ํ™•์žฅ์„ ๋“ฑ๋กํ•  ๋•Œ ์‚ฌ์šฉํ•จ.  
@TempDir ํ•„๋“œ ์ฃผ์ž… ๋˜๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜ ์ฃผ์ž…์„ ํ†ตํ•ด ์ž„์‹œ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ œ๊ณตํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•จ.  

 


# ์ปค์Šคํ…€ ์–ด๋…ธํ…Œ์ด์…˜

์ฐธ๊ณ ๋กœ Junit ๋ฉ”์„œ๋“œ๋“ค์€ ๋ฉ”ํƒ€์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ์‚ฌ์šฉํ•ด๋„ ๋œ๋‹ค.

์Šคํ”„๋ง์ฒ˜๋Ÿผ ์–ด๋…ธํ…Œ์ด์…˜ ์ƒ์†์ด ๊ฐ€๋Šฅํ•ด์„œ, ๋‚˜๋งŒ์˜ ์–ด๋…ธํ…Œ์ด์…˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค๋Š” ๋ง.

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
public @interface MyAnno {
}
@MyAnno // ์ด์ œ @Tag("fast")์™€ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค.
public Test{...}

 

์ฐธ๊ณ ๋กœ ํ…Œ์ŠคํŠธ์— @Tag๋กœ ์ด๋ฆ„์„ ๋ถ™์—ฌ๋†“์œผ๋ฉด ํŠน์ • ํ…Œ์ŠคํŠธ๋งŒ ํ•„ํ„ฐ๋งํ•ด์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ณดํ†ต @Tag("fast"), @Tag("slow") ์ฒ˜๋Ÿผ ์‹œ๊ฐ„์ด ์˜ค๋ž˜๊ฑธ๋ฆฌ๋Š” ํ…Œ์ŠคํŠธ์™€ ๋น ๋ฅธ ํ…Œ์ŠคํŠธ๋ฅผ ๊ตฌ๋ถ„ํ•ด์„œ ์‚ฌ์šฉํ•˜๊ธฐ๋„ ํ•œ๋‹ค.

์ธํ…”๋ฆฌ์ œ์ด์—์„œ ํŠน์ • ํ…Œ์ŠคํŠธ๋งŒ ์‹คํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•.

 


# Test ํด๋ž˜์Šค์™€ ๋ฉ”์†Œ๋“œ

ํ…Œ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํด๋ž˜์Šค๋Š” [์ถ”์ƒ ํด๋ž˜์Šค๊ฐ€ ์—†์œผ๋ฉฐ] [๋‹จ์ผ ์ƒ์„ฑ์ž๋ฅผ ๊ฐ€์ง€๋Š”] ํด๋ž˜์Šค์—ฌ์•ผ ํ•œ๋‹ค.

๋”ฐ๋กœ ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ํ‘œ์‹œํ•  ํ•„์š”๋Š” ์—†๊ณ , ์•ˆ์—์žˆ๋Š” ๋ฉ”์„œ๋“œ์— JUnit5 ์–ด๋…ธํ…Œ์ด์…˜์„ ์ ์šฉ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

 

  • ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ๋Š” ๋ฐ˜ํ™˜๊ฐ’์ด void์ด๋ฉฐ non-staticํ•œ ๋ฉ”์„œ๋“œ์—ฌ์•ผ ํ•œ๋‹ค.
  • ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ๋Š” @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @TestTemplate ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.
  • ๊ฐ ํ…Œ์ŠคํŠธ์˜ ์ƒ๋ช…์ฃผ๊ธฐ๋Š” @BeforeAll, @AfterAll, @BeforeEach, @AfterEach ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
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
}
@AfterEach
void tearDown() {
}
@AfterAll
static void tearDownAll() {
}
}

 


# ํ…Œ์ŠคํŠธ

์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค ๋ฐฐ์šธ๋ ค๊ณ  ํ•˜์ง€๋ง๊ณ  ๊ถ๊ธˆํ•˜๋ฉด ๊ฒ€์ƒ‰ํ•ด์„œ ์ฐพ์•„๋ณด์ž
@Test
void test() {
// ...
}
@RepeatedTest(3) // ํ•ด๋‹น ํ…Œ์ŠคํŠธ๋ฅผ 3๋ฒˆ ๋ฐ˜๋ณตํ•œ๋‹ค.
void test() {
// ...
}

 

 

 

@Transactional

๋ฉ”์„œ๋“œ์— ์ด ํ‚ค์›Œ๋“œ๋ฅผ ๋ถ™์ด๋ฉด, ํ•ด๋‹น ๋ฉ”์„œ๋“œ์— ํŠธ๋žœ์žญ์…˜์„ ์ ์šฉ์‹œ์ผœ, ํ•ด๋‹น ๋ฉ”์„œ๋“œ์•ˆ์—์„œ ์ผ์–ด๋‚œ DB์กฐ์ž‘์„ ๋ชจ๋‘ Rollback ์‹œํ‚จ๋‹ค. @Rollback(false)๋กœ ๋Œ ์ˆ˜๋„์žˆ๊ธดํ•˜๋‹ค. 

โžก ๊ฐ€๋” JPA, MyBatis๋„ ์ ์šฉ๋˜๋ƒ๊ณ  ๋ฌผ์–ด๋ณด๋Š”๋ฐ, ๊ฒฐ๊ตญ JDBC ํŠธ๋žœ์žญ์…˜(.rollback)์„ ์‚ฌ์šฉํ•˜๋Š”๊ฑฐ๋ผ ์ „๋ถ€ ๋˜‘๊ฐ™์ด ์ ์šฉ๋œ๋‹ค.

public class UserTest {
@Autowired
UserDao userDao;
UserDto dto;
@Before
public void setUp() throws Exception {
dto = new UserDto();
dto.setId("fasdf");
dto.setPasswd("dd");
dto.setName("cjung");
dto.setEmail("rokking1@naver.com");
dto.setTel("010-8680-2222");
}
@Test
@Transactional
@Rollback(true)
public void testUserController() throws Exception {
userDao.insertUser(dto);
}
}

 

 

@TestMethodOrder๋กœ ์‹คํ–‰ ์ˆœ์„œ๋ฅผ ๋ช…์‹œํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class HelloJavaTest {
@Test
@Order(2)
void test2() {
System.out.println("2");
}
@Test
@Order(1)
void test1() {
System.out.println("1");
}
}

 

 

@DisplayName("์„ค๋ช…")

ํ…Œ์ŠคํŠธ์— ์„ค๋ช…์„ ๋ถ™์ผ ์ˆ˜ ์žˆ๊ณ , @DisplayNameGeneration์œผ๋กœ ์ž๋™ ์ƒ์„ฑํ• ์ˆ˜๋„ ์žˆ๋‹ค.

์ฐธ๊ณ ๋กœ Generation ์˜ต์…˜์€ [Standard, Simple, ReplaceUnderScores, IndicativeSentences ] ๊ฐ€ ์žˆ๋‹ค.

import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Test;
@DisplayNameGeneration(DisplayNameGenerator.IndicativeSentences.class)
class HelloJavaTest {
@Test
void aTest() {
}
}

 


 

# ๋‹ค์ด๋‚˜๋ฏน ํ…Œ์ŠคํŠธ (@ParameterizedTest, @TestFactory)

@Test ๋ฅผ ์‚ฌ์šฉํ•ด๋„ ๋˜์ง€๋งŒ, ๋งค๋ฒˆ ํ…Œ์ŠคํŠธ์šฉ ํŒŒ๋ผ๋ฉ”ํƒ€ ๊ฐ’์„ ํ•œ๋•€ํ•œ๋•€ ๋ฐ•์•„๋„ฃ๋Š”๊ฑด ๊ท€์ฐฎ์€ ์ž‘์—…์ด๋‹ค.

๊ทธ๋ž˜์„œ ํ…Œ์ŠคํŠธ์šฉ ๋ฐ์ดํ„ฐ๋ฅผ ์œ ์—ฐํ•˜๊ฒŒ ๋™์ ์œผ๋กœ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” @ParameterizedTest ๊ฐ™์€๊ฒŒ ์กด์žฌํ•œ๋‹ค.

  • ๋‹ค์ด๋‚˜๋ฏน ํ…Œ์ŠคํŠธ๋ฅผ ์ด์šฉํ•˜๋ฉด, ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ์— title์„ ๋ถ™์ผ ์ˆ˜ ์žˆ์–ด ๊ฐ€๋…์„ฑ์ด ๋†’์•„์ง€๋Š” ์žฅ์ ์ด ์žˆ๋‹ค.
  • ๋‹ค๋งŒ ๋‹ค์ด๋‚˜๋ฏน ํ…Œ์ŠคํŠธ๋Š” ์ƒ๋ช…์ฃผ๊ธฐ ๊ด€๋ฆฌ๋Š” ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ๋‹ค. (@BeforeEach, @AfterEach)
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5}) // ์ด 5๋ฒˆ ์‹คํ–‰
void isUnderTenTest(int number) {
boolean result = isUnderTen(number);
assertThat(result).isTrue();
}

 

@TestFactory๋Š” private, static์ด๋ฉด ์•ˆ๋˜๊ณ , ๋ฐ˜ํ™˜๊ฐ’์œผ๋กœ๋Š” Stream, Collection, Iterable๋ฅผ ๋ฆฌํ„ดํ•ด์ค˜์•ผ ํ•œ๋‹ค.
โžก ์ด๋ฅผ ์–ด๊ธฐ๋ฉด JUnitException์ด ๋ฐœ์ƒํ•œ๋‹ค. ํ…Œ์ŠคํŠธ ๊ฐœ์ˆ˜ == ๋ฐ˜ํ™˜๊ฐ’์˜ ๊ฐœ์ˆ˜๋ผ๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.

@TestFactory
Collection<DynamicTest> dynamicTestsFromCollection() {
return Arrays.asList(dynamicTest("1st dynamic test", () -> assertEquals(6, 3 * 2)),
dynamicTest("2nd dynamic test", () -> assertEquals(4, 2 * 2)));
}
@TestFactory
Stream<DynamicTest> exampleDynamicTest() {
return Stream.of(
dynamicTest("First Dynamic Test", () -> {
// test code. @Test ์•ˆ์˜ ๋‚ด์šฉ๊ณผ ๋™์ผ
}),
dynamicTest("Second Dynamic test", () -> {
// test code. @Test ์•ˆ์˜ ๋‚ด์šฉ๊ณผ ๋™์ผ
})
);
}
@TestFactory
Stream<DynamicTest> isUnderTenTest() {
List<Integer> numbers = getNumberFromDatabase() // 1, 2, 3, 4, 5, 6, 7, 8, 9
return numbers.stream()
.map(num -> dynamicTest(num + "๊ฐ€ 10๋ฏธ๋งŒ์ธ์ง€ ๊ฒ€์‚ฌ",
() -> {
boolean result = isUnderTen(num);
assertThat(result).isTrue();
}
));
}

 

@TestFactory์˜ ๋‹ค์–‘ํ•œ ์˜ˆ์ œ

๋”๋ณด๊ธฐ
class DynamicTestsDemo {
private final Calculator calculator = new Calculator();
// This will result in a JUnitException!
@TestFactory
List<String> dynamicTestsWithInvalidReturnType() {
return Arrays.asList("Hello");
}
@TestFactory
Collection<DynamicTest> dynamicTestsFromCollection() {
return Arrays.asList(
dynamicTest("1st dynamic test", () -> assertTrue(isPalindrome("madam"))),
dynamicTest("2nd dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))
);
}
@TestFactory
Iterable<DynamicTest> dynamicTestsFromIterable() {
return Arrays.asList(
dynamicTest("3rd dynamic test", () -> assertTrue(isPalindrome("madam"))),
dynamicTest("4th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))
);
}
@TestFactory
Iterator<DynamicTest> dynamicTestsFromIterator() {
return Arrays.asList(
dynamicTest("5th dynamic test", () -> assertTrue(isPalindrome("madam"))),
dynamicTest("6th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))
).iterator();
}
@TestFactory
DynamicTest[] dynamicTestsFromArray() {
return new DynamicTest[] {
dynamicTest("7th dynamic test", () -> assertTrue(isPalindrome("madam"))),
dynamicTest("8th dynamic test", () -> assertEquals(4, calculator.multiply(2, 2)))
};
}
@TestFactory
Stream<DynamicTest> dynamicTestsFromStream() {
return Stream.of("racecar", "radar", "mom", "dad")
.map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text))));
}
@TestFactory
Stream<DynamicTest> dynamicTestsFromIntStream() {
// Generates tests for the first 10 even integers.
return IntStream.iterate(0, n -> n + 2).limit(10)
.mapToObj(n -> dynamicTest("test" + n, () -> assertTrue(n % 2 == 0)));
}
@TestFactory
Stream<DynamicTest> generateRandomNumberOfTestsFromIterator() {
// Generates random positive integers between 0 and 100 until
// a number evenly divisible by 7 is encountered.
Iterator<Integer> inputGenerator = new Iterator<Integer>() {
Random random = new Random();
int current;
@Override
public boolean hasNext() {
current = random.nextInt(100);
return current % 7 != 0;
}
@Override
public Integer next() {
return current;
}
};
// Generates display names like: input:5, input:37, input:85, etc.
Function<Integer, String> displayNameGenerator = (input) -> "input:" + input;
// Executes tests based on the current input value.
ThrowingConsumer<Integer> testExecutor = (input) -> assertTrue(input % 7 != 0);
// Returns a stream of dynamic tests.
return DynamicTest.stream(inputGenerator, displayNameGenerator, testExecutor);
}
@TestFactory
Stream<DynamicTest> dynamicTestsFromStreamFactoryMethod() {
// Stream of palindromes to check
Stream<String> inputStream = Stream.of("racecar", "radar", "mom", "dad");
// Generates display names like: racecar is a palindrome
Function<String, String> displayNameGenerator = text -> text + " is a palindrome";
// Executes tests based on the current input value.
ThrowingConsumer<String> testExecutor = text -> assertTrue(isPalindrome(text));
// Returns a stream of dynamic tests.
return DynamicTest.stream(inputStream, displayNameGenerator, testExecutor);
}
@TestFactory
Stream<DynamicNode> dynamicTestsWithContainers() {
return Stream.of("A", "B", "C")
.map(input -> dynamicContainer("Container " + input, Stream.of(
dynamicTest("not null", () -> assertNotNull(input)),
dynamicContainer("properties", Stream.of(
dynamicTest("length > 0", () -> assertTrue(input.length() > 0)),
dynamicTest("not empty", () -> assertFalse(input.isEmpty()))
))
)));
}
@TestFactory
DynamicNode dynamicNodeSingleTest() {
return dynamicTest("'pop' is a palindrome", () -> assertTrue(isPalindrome("pop")));
}
@TestFactory
DynamicNode dynamicNodeSingleContainer() {
return dynamicContainer("palindromes",
Stream.of("racecar", "radar", "mom", "dad")
.map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text)))
));
}
}

 

 

Mockito๋ฅผ ์ด์šฉํ•ด์„œ HTTP API ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๋Š” ์˜ˆ์ œ

๋”๋ณด๊ธฐ
@ParameterizedTest
@NullAndEmptySource // args์— null๊ณผ ๋นˆ๊ฐ’์„ ํ…Œ์ŠคํŠธํ•จ. (๊ทธ๋ž˜์„œ ์ด 2๋ฒˆ ์‹คํ–‰)
@DisplayName("HTTP ํŒŒ๋ผ๋ฉ”ํƒ€๊ฐ€ ๋น„์—ˆ๊ฑฐ๋‚˜ null์ธ ๊ฒ€์ฆ์š”์ฒญ์„ ๋ณด๋‚ธ ๊ฒฝ์šฐ 400 HTTP Code ๋ฆฌํ„ดํ•œ๋‹ค.")
void http_param_is_empty_or_null(String args) throws Exception {
// Given
var request = new ConfirmRegisterAccountRequest();
request.setToken(args);
request.setEmail(args);
// When -> Mockito ์‚ฌ์šฉ
doThrow(IllegalStateException.class).when(accountFacade).registerConfirm(request);
final var actions = mockMvc.perform(get("/api/accounts/authorize")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.param("token", args)
.param("email", args)
);
// Then -> Mockito ์‚ฌ์šฉ
actions
.andDo(print())
.andExpect(status().isBadRequest());
}
@TestFactory
@DisplayName("์ด๋ฉ”์ผ ๊ฒ€์ฆ ์‹œ๋‚˜๋ฆฌ์˜ค")
Collection<DynamicTest> verify_account_scenarios() {
var account = accountRepository.save(
Account.of("jiwonDev@gmail.com", "jiwon", "password!"));
return Arrays.asList(
DynamicTest.dynamicTest("ํšŒ์› ์ด๋ฉ”์ผ ํ† ํฐ๊ฒ€์ฆ์— ์„ฑ๊ณตํ•œ๋‹ค.", () -> {
//Arrange
var command = new ConfirmRegisterAccountCommand(account.getEmailCheckToken(),
account.getEmail());
// Act
confirmRegisterAccountProcessor.registerConfirm(command);
Account updatedAccount = accountRepository.findByEmail(account.getEmail());
// Assert
assertThat(updatedAccount.isEmailVerified()).isTrue();
}),
DynamicTest.dynamicTest("์ด๋ฉ”์ผ์ด๋‚˜ ํ† ํฐ์˜ ๊ฐ’์ด null์ธ ๊ฒฝ์šฐ ์˜ˆ์™ธ๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.",
() -> {
//Arrange
var command = new ConfirmRegisterAccountCommand(null, null);
// Act & Assert
assertThatExceptionOfType(NullPointerException.class)
.isThrownBy(() -> confirmRegisterAccountProcessor.registerConfirm(command));
}),
DynamicTest.dynamicTest("์ด๋ฉ”์ผ ๊ฒ€์ฆ์— ์‹คํŒจํ•˜์—ฌ ์˜ˆ์™ธ๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.",
() -> {
//Arrange
var command = new ConfirmRegisterAccountCommand(account.getEmailCheckToken(),
"HiIamNotExistEmail@naver.com");
// Act & Assert
assertThatExceptionOfType(IllegalStateException.class)
.isThrownBy(() -> confirmRegisterAccountProcessor.registerConfirm(command));
}),
DynamicTest.dynamicTest("ํ† ํฐ ๊ฒ€์ฆ์— ์‹คํŒจํ•˜์—ฌ ์˜ˆ์™ธ๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.",
() -> {
//Arrange
var command = new ConfirmRegisterAccountCommand("์•ˆ๋…•๋‚œ๊ฐ€์งœํ† ํฐ์ด๋ผ๊ณ ํ•ด",
account.getEmail());
// Act & Assert
assertThatExceptionOfType(IllegalStateException.class)
.isThrownBy(() -> confirmRegisterAccountProcessor.registerConfirm(command));
})
);
}

 

 

# ๋‹ค์ด๋‚˜๋ฏน ํ…Œ์ŠคํŠธ์— ์‚ฌ์šฉํ•˜๋Š” ๊ฐ’ ์ƒ์„ฑ @์–ด๋…ธํ…Œ์ด์…˜

@ValueSource ( types = {...} )

ํ•˜๋‚˜์˜ ํƒ€์ž…์˜ ๊ฐ’์„ ์ˆœ์„œ๋Œ€๋กœ ์ „๋‹ฌํ•˜์—ฌ ์—ฌ๋Ÿฌ๋ฒˆ ํ…Œ์ŠคํŠธํ•œ๋‹ค.

types๋Š” [ short, byte, int, long, float, double, char, java.lang.String, java.lang.Class ], ์ฆ‰ ๋ชจ๋“  ํƒ€์ž…์„ ์ œ๊ณตํ•ด์ค€๋‹ค.

@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5}) // ์ด 5๋ฒˆ ์‹คํ–‰
void isUnderTenTest(int number) {
boolean result = isUnderTen(number);
assertThat(result).isTrue();
}
@ParameterizedTest
@ValueSource(strings = {"", " "}) // ์ด 2๋ฒˆ ์‹คํ–‰
void isBlank_ShouldReturnTrueForNullOrBlankStrings(String input) {
assertTrue(Strings.isBlank(input));
}

 

 

@NullSoruce, @EmptySource, @NullAndEmptySource

์ฐธ๊ณ ๋กœ NullSoruce๋Š” ๊ธฐ๋ณธํƒ€์ž… (primitive)์— ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค. ๋‹น์—ฐํ•œ ์‚ฌ์‹ค.

@ParameterizedTest
@NullAndEmptySource
void isBlank_ShouldReturnTrueForNullAndEmptyStrings(String input) {
assertTrue(Strings.isBlank(input));
}

 

 

@EnumSoruce

@ParameterizedTest // names๊ฐ€ ์—†๋‹ค๋ฉด ENUM์— ์กด์žฌํ•˜๋Š” ๋ชจ๋“  ๊ฐ’์„ ํ…Œ์ŠคํŠธํ•œ๋‹ค.
@EnumSource(value = Month.class, names = {"APRIL", "JUNE", "SEPTEMBER", "NOVEMBER"})
void someMonths_Are30DaysLong(Month month) {
final boolean isALeapYear = false;
assertEquals(30, month.length(isALeapYear));
}
@ParameterizedTest
@EnumSource(
value = Month.class,
names = {"APRIL", "JUNE", "SEPTEMBER", "NOVEMBER", "FEBRUARY"},
mode = EnumSource.Mode.EXCLUDE) // names์— ์žˆ๋Š” ๊ฐ’์„ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€๋ฅผ ํ…Œ์ŠคํŠธํ•œ๋‹ค.
void exceptFourMonths_OthersAre31DaysLong(Month month) {
final boolean isALeapYear = false;
assertEquals(31, month.length(isALeapYear));
}

 

 

@ MethodSource (์ง์ ‘ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ๋งŒ๋“ค๊ธฐ)

Stream, Iterable์„ ๋ฐ˜ํ™˜ํ•˜๋Š” static method๋ฅผ ๋งŒ๋“ค์–ด ์ง์ ‘ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฐธ๊ณ ๋กœ junit.ArgumentsProvider๋ฅผ ์ƒ์†ํ•ด์„œ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด๋‘๊ณ  @ArgumentSource(my.class)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๋‹ค.

private static Stream<Arguments> provideStringsForIsBlank() { // argument source method
return Stream.of(
Arguments.of(null, true),
Arguments.of("", true),
Arguments.of(" ", true),
Arguments.of("not blank", false)
);
}
@ParameterizedTest
@MethodSource("provideStringsForIsBlank") // ํ…Œ์ŠคํŠธ ๊ฐ์ฒด์•ˆ์— ํ•ด๋‹น ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ์–ด์•ผํ•จ.
void isBlank_ShouldReturnTrueForNullOrBlankStrings(String input, boolean expected) {
assertEquals(expected, Strings.isBlank(input));
}
// ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค์•ˆ์— ์žˆ๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด, ํŒจํ‚ค์ง€๋ช….๊ฐ์ฒด๋ช…#๋ฉ”์„œ๋“œ๋ช… ์œผ๋กœ ์ ์–ด์•ผ ํ•œ๋‹ค.
@ParameterizedTest
@MethodSource("com.baeldung.parameterized.StringParams#provideStringsForIsBlank")
void isBlank_ShouldReturnTrueForNullOrBlankStringsExternalSource(String input) {
assertTrue(Strings.isBlank(input));
}

 

@CSVSource

CSV๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. value, delimiter, delimiterString, emptyValue(๋Œ€์ฒด๊ฐ’), nullValue(๋Œ€์ฒด๊ฐ’)๋“ฑ์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.


# JUnit5.Assertions ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•œ ํ…Œ์ŠคํŠธ

Assertions ์•ˆ์—๋Š” ํ…Œ์ŠคํŠธ ๊ฒ€์ฆ์„ ์œ„ํ•œ ๋‹ค์–‘ํ•œ ๋ฉ”์„œ๋“œ๊ฐ€ ์กด์žฌํ•œ๋‹ค.

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

 

๊ทผ๋ฐ ์‚ฌ์šฉ๋ฒ•์„ ๋ณด๋ฉด ์•Œ๊ฒ ์ง€๋งŒ JUnit ๊ธฐ๋ณธ Assertions๋Š” ๊ตฌ๋ฆฌ๋‹ค.

JUnit5.Assertions๋Š” ํ•จ์ˆ˜ ์ฒด์ด๋‹์ด ์•ˆ๋˜์„œ ์ฝ๊ธฐ๋„ ํž˜๋“ค๊ณ , IDE๊ฐ€ ์ž๋™์™„์„ฑ๋„ ์ง€์›ํ•˜์ง€ ์•Š๋Š”๋‹ค.

๊ทธ๋ž˜์„œ ๋ณดํ†ต ์„œ๋“œํŒŒํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ (AssertJ.Assertions)๋ฅผ ๋Œ€์‹  ์‚ฌ์šฉํ•œ๋‹ค.

 

 


 

# AssertJ.Assertions ๋ฉ”์„œ๋“œ ์•Œ์•„๋ณด๊ธฐ

JUnit5์™€ ํ˜ธํ™˜๊ฐ€๋Šฅํ•œ Thrid-Party Assertion ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ด ์กด์žฌํ•œ๋‹ค. 

๊ทธ์ค‘ ๋Œ€ํ‘œ์ ์ธ๊ฒŒ AssertJ๋กœ ์ถ”๊ฐ€์ ์ธ ํ…Œ์ŠคํŠธ ๋ฌธ๋ฒ•๊ณผ ๋ฉ”์„œ๋“œ ์ฒด์ด๋‹์„ ์ œ๊ณตํ•ด์ฃผ๋Š” ์˜คํ”ˆ์†Œ์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค.

์ฐธ๊ณ ๋กœ Hamcrest, Truth๋ผ๊ณ  ๋น„์Šทํ•œ๊ฒŒ ์žˆ๊ธดํ•œ๋ฐ, ๋ณดํ†ต AssertJ๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค. ์ œ์ผ ์ง๊ด€์ ์ด๊ณ  ํŽธํ•˜๋‹ค.

 

์ฐธ๊ณ ๋กœ ์ด๋Š” JUnit5 ํŒ€์—์„œ๋„ ๊ณต์‹์ ์œผ๋กœ ์ถ”์ฒœํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ผ์„œ, ํ˜ธํ™˜์„ฑ์€ ๊ฑฑ์ •ํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

 

 

@ assertThat, assertAll

assertThat(...)์€ ํ•ด๋‹น ๊ฐ’์˜ ๊ฒ€์ฆ์ด ์‹คํŒจํ•˜๋ฉด ํ…Œ์ŠคํŠธ ์‹คํŒจ๋ฅผ ์•Œ๋ฆฌ๊ณ , ํ•ด๋‹น ๋ฉ”์„œ๋“œ์˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ค‘์ง€์‹œํ‚จ๋‹ค.

assertThat(actual).isEqualTo(expected);

 

๊ธฐ์กด์—๋Š” assertEqual, assertTrue, assertFalse, assertNull ๊ฐ™์ด ๋‹ค์–‘ํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ–ˆ์—ˆ๋‹ค.

ํ•˜์ง€๋งŒ assertThat๋ฅผ ์ค‘๊ฐ„์— ์ถ”๊ฐ€ํ–ˆ๋Š”๋ฐ, ๊ทธ ์ด์œ ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์“ฐ๋Š”๊ฒŒ ํ›จ์”ฌ ๊ฐ„๊ฒฐํ•˜๊ณ  ์ฝ๊ธฐ์ข‹๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

@Test // ์šฐ๋ฆฌ๋„ ๋˜๋„๋ก์ด๋ฉด ๊ทธ๋ƒฅ assertThat์œผ๋กœ ํ†ต์ผํ•ด์„œ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์“ฐ๋„๋ก ํ•˜์ž.
void a_few_simple_assertions() {
assertThat("The Lord of the Rings").isNotNull()
.startsWith("The")
.contains("Lord")
.endsWith("Rings");
}

 

assertAll(...)์€ ์ž๋ฐ”์˜ ํ•จ์ˆ˜ํ˜• ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ด์šฉํ•ด ์—ฌ๋Ÿฌ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๋‚˜๋กœ ๋ฌถ์–ด์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

@Test
void test1() {
assertAll(
//A Case
() -> assertFalse(true, "Exception!!!"),
//B Case
() -> {
TestObject tObj = null;
assertNotNull(tObj, "Object is Null!!");
}
);
}

 

 

@ String

์ฐธ๊ณ ๋กœ String ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ isTrue, isFalse๊ฐ™์ด ์–ด์ง€๊ฐ„ํ•œ ๊ธฐ๋ณธ ๋ฉ”์„œ๋“œ๋Š” ๋‹ค ์žˆ์œผ๋‹ˆ, ์ž๋™์™„์„ฑ์œผ๋กœ ์ฐพ์•„๋ณด๋„๋ก ํ•˜์ž.

@Test
void ๋ฌธ์ž์—ด_๊ฒ€์ฆ() {
String expression = "This is a string";
assertThat(expression).startsWith("This").endsWith("string").contains("a");
}

 

 

@ ํ…Œ์ŠคํŠธ ์‹คํŒจ ๋ฉ”์‹œ์ง€ ์ง€์ • (Description)

@Test
void test() throws Exception {
String str = "name";
// .as๋ฅผ ์ด์šฉํ•˜์—ฌ ํ•ด๋‹น ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ–ˆ์„ ๋•Œ ๋ณด์—ฌ์ค„ ์„ค๋ช…์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
assertThat(str).as("๊ฐ’์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”. ํ˜„์žฌ ๊ฐ’: %s", str)
.isEqualTo("name2");
}

 

 

@ cotains, containsOnly, containsExactly

contains์€ ํ•ด๋‹น ๊ฐ’๋“ค์ด ์ „๋ถ€ ๊ฒ€์ฆ๊ฐ์ฒด์— ์กด์žฌํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ•˜๋Š” ํ•จ์ˆ˜์ด๋‹ค.

void containsTest() {
List<Integer> list = Arrays.asList(1, 2, 3);
// Success: ๋ชจ๋“  ์›์†Œ๋ฅผ ์ž…๋ ฅํ•˜์ง€ ์•Š์•„๋„ ์„ฑ๊ณต
assertThat(list).contains(1, 2);
// Success: ์ค‘๋ณต๋œ ๊ฐ’์ด ์žˆ์–ด๋„ ํฌํ•จ๋งŒ ๋˜์–ด ์žˆ์œผ๋ฉด ์„ฑ๊ณต
assertThat(list).contains(1, 2, 2);
// Success: ์ˆœ์„œ๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ ๊ฐ’๋งŒ ๋งž์œผ๋ฉด ์„ฑ๊ณต
assertThat(list).contains(3, 2);
// Fail: List ์— ์—†๋Š” ๊ฐ’์„ ์ž…๋ ฅํ•˜๋ฉด ์‹คํŒจ
assertThat(list).contains(1, 2, 3, 4);
}

 

๋งŒ์•ฝ ์•ˆ์— ์žˆ๋Š” ๊ฐ’๋“ค์„ ์ •ํ™•ํ•˜๊ฒŒ ๊ฒ€์ฆํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ContainsOnly๋ฅผ ์‚ฌ์šฉํ•˜์ž. (์ˆœ์„œ์™€ ์ค‘๋ณต์€ ๊ณ ๋ คํ•˜์ง€ ์•Š๋Š”๋‹ค)

@Test
void containsOnlyTest() {
List<Integer> list = Arrays.asList(1, 2, 3);
assertThat(list).containsOnly(1, 2, 3);
assertThat(list).containsOnly(3, 2, 1); // ์ˆœ์„œ๋Š” ๊ณ ๋ คํ•˜์ง€ ์•Š๋Š”๋‹ค.
assertThat(list).containsOnly(1, 2, 3, 3); // ์ค‘๋ณต์€ ๊ณ ๋ คํ•˜์ง€ ์•Š๋Š”๋‹ค. (1,2,3) ์œผ๋กœ ์ธ์‹
// (1,2) ๋‚˜ (1,2,3,4)๋Š” ๊ฒ€์ฆ์— ์‹คํŒจํ•œ๋‹ค.
}

 

์ˆœ์„œ์™€ ์ค‘๋ณต์—ฌ๋ถ€๊นŒ์ง€ ๊ณ ๋ คํ•ด์„œ ์™„๋ฒฝํ•œ ์ผ์น˜๋ฅผ ์›ํ•œ๋‹ค๋ฉด containsExactly๋ฅผ ์‚ฌ์šฉํ•˜์ž

@Test
void containsExactlyTest() {
List<Integer> list = Arrays.asList(1, 2, 3);
assertThat(list).containsExactly(1, 2, 3);
// (1,2) (3,2,1) (1,2,3,3) ๊ฐ™์€ ๊ฑด ๊ฒ€์ฆ ์‹คํŒจ
}

 

 

@ ํ…Œ์ŠคํŠธ ์‹คํŒจ ๋ฉ”์‹œ์ง€, ์ฝ”๋ฉ˜ํŠธ, ์ฃผ์„ (Description, Comment)

* assertThat(...)๋‹ค์Œ์— ๋ฐ”๋กœ ์ ์–ด์•ผ ๋™์ž‘ํ•œ๋‹ค. ํ•จ์ˆ˜ ์ฒด์ด๋‹ ๊ตฌํ˜„์ƒ ์ค‘๊ฐ„์— ์ ์œผ๋ฉด ๋™์ž‘ํ•˜์ง€ ์•Š๊ณ  ๋ฌด์‹œ๋œ๋‹ค.

@Test
void test() throws Exception {
String str = "name";
assertThat(str).as("๊ฐ’์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”. ํ˜„์žฌ ๊ฐ’: %s", str)
.isEqualTo("name2");
}

 

์‹คํŒจ์‹œ ๋ฉ”์‹œ์ง€๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ, ์•„์˜ˆ AssertionError์˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณ€๊ฒฝํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด .withFailMessage()๋ฅผ ์‚ฌ์šฉํ•˜์ž.

assertThat(frodo.getAge()).withFailMessage("should be %s", frodo)
.isEqualTo(sam);
// java.lang.AssertionError: should be TolkienCharacter [name=Frodo, age=33, race=HOBBIT]

 

ํ•ญ์ƒ ์ถœ๋ ฅ(์„ฑ๊ณตํ–ˆ์„ ๋•Œ๋„ ์ถœ๋ ฅ)ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋”ฐ๋กœ ์—†์œผ๋‚˜, .as(..)์— ์‚ฌ์šฉ๋œ ๋ฉ”์‹œ์ง€๋ฅผ ๋ชจ์•„๋‘๋Š”๊ฑด ๊ฐ€๋Šฅํ•˜๋‹ค.

// 1. ๋ฉ”์‹œ์ง€๋ฅผ ํ•˜๋‚˜๋กœ ํ•ฉ์น  StringBuilder๋ฅผ ๋งŒ๋“ ๋‹ค. (*String์œผ๋กœ ํ•ด๋„ ๋˜์ง€๋งŒ, ๋ถˆ๋ณ€์ด๋ผ ์„ฑ๋Šฅ์ด ๋‚˜๋น ์ง„๋‹ค.)
final StringBuilder descriptions = new StringBuilder(String.format("Assertions:%n"));
// 2. assertThat์„ ํ•˜๊ธฐ ์ „์— as(..msg..)์— ๋“ฑ๋ก๋œ ๋ฉ”์‹œ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•  ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•˜๊ณ  ๋“ฑ๋กํ•œ๋‹ค.
Assertions.setDescriptionConsumer( msg -> descriptions.append(String.format("-- %s%n", msg)) );
var frodo = new TolkienCharacter("Frodo", 33, Race.HOBBIT);
assertThat(frodo.getName()).as("check name").isEqualTo("Frodo");
assertThat(frodo.getAge()).as("check age").isEqualTo(33);
// 3. ๋งŒ๋“ค์–ด์ง„ ๋ฌธ์ž์—ด์„ ์ง์ ‘ ํ™•์ธํ•˜๋ฉด ๋œ๋‹ค.
System.out.println(descriptions);
์ฝ˜์†”์—๋Š” ์ด๋ ‡๊ฒŒ ์ฐํžŒ๋‹ค.

 

@ Collection Filtering (ํ•„ํ„ฐ๋ง)

Array๋‚˜ iterable์— ๋‹ด๊ธด ๊ฐ’์„ ๊บผ๋‚ด์„œ ๋น„๊ตํ•  ์ˆ˜ ์žˆ๋‹ค. stream()๊ณผ ์‚ฌ์šฉ๋ฒ•์ด ๋น„์Šทํ•œ๋ฐ, ์˜ˆ์ œ๋ฅผ ์‚ดํŽด๋ณด์ž

@Getter
@AllArgsConstructor
public class Member {
private String name;
private int age;
private MemberType type;
}
enum MemberType{
ADMIN, USER
}
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.util.Lists.newArrayList;
class MemberTest {
private Member dexter = new Member("dexter", 12, ADMIN);
private Member james = new Member("james", 30, ADMIN);
private Member park = new Member("park", 23, USER);
private Member lee = new Member("lee", 33, USER);
private List<Member> members = newArrayList(dexter, james, park, lee);
@Test
void sample() throws Exception {
// 1. filteredOn์œผ๋กœ ์กฐ๊ฑด์„ ๊ฑธ๊ณ , containsOnly๋กœ ๊ฒ€์ฆํ•œ๋‹ค.
assertThat(members)
.filteredOn("type", ADMIN) // "type"์•ˆ์— ์žˆ๋Š” ๊ฐ’ ํƒ€์ž… == ADMIN
.containsOnly(dexter, james);
// 2. ๋žŒ๋‹ค์‹ ์‚ฌ์šฉ
assertThat(members)
.filteredOn(m -> m.getType() == USER) // m.getType == USER์ธ๊ฑด
.containsOnly(park, lee);
// 3 in ์กฐ๊ฑด (์—ฌ๋Ÿฌ๊ฐœ)
assertThat(members).
filteredOn("type", in(ADMIN, USER)) // "type" in(ADMIN,USER)
.containsOnly(dexter, james, park, lee);
// 4 not ์กฐ๊ฑด
assertThat(members)
.filteredOn("type", not(ADMIN)) // not(ADMIN)
.containsOnly(park, lee);
// 5. ์กฐ๊ฑด์ด ์—ฌ๋Ÿฌ๊ฐœ์ผ ๋• ์ฒด์ธํ•ด์„œ ๊ฑธ๋ฉด ๋œ๋‹ค.
assertThat(members)
.filteredOn("type", ADMIN)
.filteredOn(m -> m.getAge() > 20)
.containsOnly(james);
}
}

 

 

@ Collection Data Extracting

MemberList์—์„œ member๋ฅผ ๊บผ๋‚ด ๊ฐ๊ฐ์˜ name์„ ๊ฒ€์ฆํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ด๋ณด์ž.

@Test
void no_extracting() throws Exception{
List<String> names = new ArrayList<>();
for (Member member : members) {
names.add(member.getName());
}
assertThat(names).containsOnly("dexter", "james", "park", "lee");
}

๋” ๊ฐ„๊ฒฐํ•œ ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค. extracting("fieldname")์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์ปฌ๋ ‰์…˜ ํ•ด์ฒด ๋ฉ”์„œ๋“œ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด๋œ๋‹ค. 

@Test
void extracting_more() throws Exception {
// 1
assertThat(members)
.extracting("name", String.class)
.contains("dexter", "james", "park", "lee");
// 2
assertThat(members) // ๊ฒ€์ฆ ํ•„๋“œ๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ๋ผ๋ฉด tuple์„ ์‚ฌ์šฉํ•˜์ž.
.extracting("name", "age")
.contains(
tuple("dexter", 12),
tuple("james", 30),
tuple("park", 23),
tuple("lee", 33)
);
}

 

 

@ Soft Assrtion

์•ž์˜ ์˜ˆ์ œ์—์„œ ์‚ฌ์šฉํ•œ asserThat์€ ๊ฒ€์ฆ์— ์‹คํŒจํ•˜๋ฉด ํ•ด๋‹น ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ๋ฅผ ๋ฐ”๋กœ ์ค‘์ง€์‹œ์ผœ์„œ ๋ถˆํŽธํ•˜๋‹ค.

  • assertAll๋กœ ๋žŒ๋‹ค์‹์œผ๋กœ ๋ชจ๋“  ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๋‚˜๋กœ ๋ฌถ๋Š” ๋ฐฉ๋ฒ•๋„์žˆ์ง€๋งŒ, SoftAssertions์„ ์“ฐ๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๋‹ค.
  • SoftAssertions.assertSoftly(...)๋ฅผ ์ด์šฉํ•˜๋ฉด ์‹คํŒจํ•˜๋”๋ผ๋„ ์ค‘์ง€ํ•˜์ง€ ์•Š๊ณ  ๋ชจ๋“  ํ…Œ์ŠคํŠธ๋ฅผ ๋‹ค ๋Œ๋ ค๋ณผ ์ˆ˜ ์žˆ๋‹ค.
@Test
void basic_soft_assertions_example() {
SoftAssertions softly = new SoftAssertions();
softly.assertThat("George Martin").as("great authors").isEqualTo("JRR Tolkien");
softly.assertThat(42).as("response to Everything").isGreaterThan(100);
softly.assertThat("Gandalf").isEqualTo("Sauron");
// ๐Ÿงจ์ฃผ์˜!! assertAll()์„ ํ•˜์ง€์•Š์œผ๋ฉด, ๊ฒ€์ฆ๋˜์ง€ ์•Š๋Š”๋‹ค.
softly.assertAll();
}
@Test
void basic_bdd_soft_assertions_example() {
BDDSoftAssertions softly = new BDDSoftAssertions();
softly.then("George Martin").as("great authors").isEqualTo("JRR Tolkien");
softly.then(42).as("response to Everything").isGreaterThan(100);
softly.then("Gandalf").isEqualTo("Sauron");
// ๐Ÿงจ์ฃผ์˜!! assertAll()์„ ํ•˜์ง€์•Š์œผ๋ฉด, ๊ฒ€์ฆ๋˜์ง€ ์•Š๋Š”๋‹ค.
softly.assertAll();
}

 

๋งค๋ฒˆ SoftAssertions ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๊ณ , assertAll()์„ ํ˜ธ์ถœํ•˜๊ธฐ ๊ท€์ฐฎ๋‹ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด @InjectSoftAssertions๋ฅผ ์‚ฌ์šฉํ•˜์ž.
SoftAssertionsExtension ๊ฐ์ฒด๊ฐ€ SoftAssertionsProvider ๋ผ๋Š”๊ฑธ ์ฃผ์ž…ํ•ด์ฃผ๋Š”๋ฐ, ์ด๋Š” BDDSoft์™€ ๊ทธ๋ƒฅ Soft๋ฅผ ๋‹ค ์ผ๊ด„์ฒ˜๋ฆฌํ•ด์ค€๋‹ค.

@ExtendWith(SoftAssertionsExtension.class)
public class JUnit5SoftAssertionsExtensionAssertionsExamples {
@InjectSoftAssertions
private SoftAssertions softly; // ์ด๋ ‡๊ฒŒํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ ๊น”๋”ํ•ด์ง„๋‹ค.
@Test
public void chained_soft_assertions_example() {
String name = "Michael Jordan - Bulls";
softly.assertThat(name).startsWith("Mi")
.contains("Bulls");
// softly.assertAll() ๋ฅผ ๋”ฐ๋กœ ํ˜ธ์ถœํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค. ์•Œ์•„์„œ ๊ฒ€์ฆํ•ด์คŒ
}
@ParameterizedTest
@CsvSource({ "1, 1, 2", "1, 2, 3" })
void junit5_soft_assertions_parameterized_test_example(int a, int b, int sum, SoftAssertions softly) {
softly.assertThat(a + b).as("sum").isEqualTo(sum);
softly.assertThat(a).isLessThan(sum);
softly.assertThat(b).isLessThan(sum);
}
}

 

๋งŒ์•ฝ @InjectSoftAssertion ๊ฐ™์ด ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ๊ผด๋ณด๊ธฐ ์‹ซ๋‹ค๋ฉด, ๊ทธ๋ƒฅ assertSoftly๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๋‹ค.
๋Œ€์‹  ์•„๋ž˜์™€ ๊ฐ™์ด ๋ฉ”์„œ๋“œ๋กœ ๋ฐ˜ํ™˜ํ•ด์ค˜์•ผํ•จ

@Test
void assertSoftly_example() {
SoftAssertions.assertSoftly(softly -> {
softly.assertThat("George Martin").as("great authors").isEqualTo("JRR Tolkien");
softly.assertThat(42).as("response to Everything").isGreaterThan(100);
softly.assertThat("Gandalf").isEqualTo("Sauron");
// ์ด ์—ญ์‹œ assertAll() ๋ฅผ ํ˜ธ์ถœํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค. assertSoftly ๊ฐ€ ์•Œ์•„์„œ ํ•ด์คŒ.
});
}

 

์ฐธ๊ณ ๋กœ assertAll ๊ฒ€์ฆ๊ฒฐ๊ณผ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌ๋ถ„ํ•ด์„œ ๋‚˜์˜จ๋‹ค.
๋งŒ์•ฝ ๊ฐ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๋ฅผ ๋ถ„๋ฆฌํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด Junit์˜ DynamicTest๋ฅผ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค.

 

 

 

@ File Assertions

ํŒŒ์ผ ๊ฐ์ฒด๋ฅผ ํ…Œ์ŠคํŠธ ํ•  ์ˆ˜ ์žˆ๋‹ค. ์‚ฌ์šฉ๋ฒ•์€ ํฌ๊ฒŒ ๋‹ค๋ฅด์ง€์•Š๋‹ค.

@Test
void file() throws Exception{
File file = writeFile("Temp", "You Know Nothing Jon Snow");
// 1 File ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•ด ์กด์žฌ์—ฌ๋ถ€, ํŒŒ์ผ์—ฌ๋ถ€, ์ƒ๋Œ€๊ฒฝ๋กœ ์—ฌ๋ถ€๋“ฑ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
assertThat(file).exists().isFile().isRelative();
// 2
assertThat(contentOf(file)) // ํ•ด๋‹น ํŒŒ์ผ์˜ ๋‚ด์šฉ์„ ์ฝ์–ด์„œ ๊ฒ€์ฆํ•œ๋‹ค.
.startsWith("You")
.contains("Know Nothing")
.endsWith("Jon Snow");
}
private File writeFile(String fileName, String fileContent) throws Exception {
File file = new File(fileName);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), Charset.defaultCharset()));
writer.write(fileContent);
writer.close();
return file;
}

 

 

@ Exception Assertions (์˜ˆ์™ธ ๊ฒ€์ฆ)

assertThatThrownBy ๋˜๋Š” catchThrowable( () -> {} )๋กœ ์ž๋ฐ” ์˜ˆ์™ธ๊ฐ์ฒด๋ฅผ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋‹ค.

private void throwException() throws Exception {
throw new Exception("์˜ˆ์™ธ ๋˜์ง€๊ธฐ!22");
}
// 1
@Test
void exception() throws Exception {
assertThatThrownBy(() -> throwException())
.isInstanceOf(Exception.class)
.hasMessage("์˜ˆ์™ธ ๋˜์ง€๊ธฐ!") // Exception ๊ฐ์ฒด๊ฐ€ ๊ฐ€์ง€๊ณ ์žˆ๋Š” ๋ฉ”์‹œ์ง€ ๊ฒ€์ฆ
.hasMessageEndingWith("Size: 2")
.hasMessageMatching("Index: \\d+, Size: \\d+") // String ๋งˆ๋ƒฅ ์ด๋Ÿฐ ๊ฒ€์ฆ๋„ ๊ฐ€๋Šฅ.
.hasStackTraceContaining("Exception");
}
// 2 ๋˜๋Š” ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๋‹ค. BDD์˜ When๊ณผ Then์„ ๊ตฌ๋ถ„ํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.
@Test
void thrownTest() throws Exception {
// When (catchThorwable์— ์ฝœ๋ฐฑ๋ฉ”์„œ๋“œ๋กœ ์ „๋‹ฌํ•˜์—ฌ ์‹คํ–‰์„ ๋Šฆ์ถ˜๋‹ค.)
Throwable throwable = catchThrowable(() -> throwException());
// Then (์ง€๊ธˆ ์‹คํ–‰ํ•˜๊ณ  ๊ฒ€์ฆ)
assertThat(throwable).isInstanceOf(Exception.class).hasMessage("์˜ˆ์™ธ ๋˜์ง€๊ธฐ!");
}

 

์ฐธ๊ณ ๋กœ ์ž๋ฐ”์—์„œ ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋“ค์€ ์•„๋ž˜์™€ ๊ฐ™์ด assertThatException์ด ์กด์žฌํ•œ๋‹ค.

assertThatIOException().isThrownBy(() -> {})
assertThatIllegalArgumentException.isThrownBy(() -> {})
assertThatIllegalStateException().isThrownBy(() -> {})
assertThatIOException().isThrownBy(() -> {})
assertThatNullPointerException().isThrownBy(() -> {})

 

 

@  Compare ํ•จ์ˆ˜ (Comparsion) ์ปค์Šคํ…€ํ•˜๊ธฐ

๋น„๊ตํ• ๋•Œ ์‚ฌ์šฉํ•˜๋Š” Comparator.compare๋ฅผ ์ง์ ‘ ์ปค์Šคํ…€ํ•  ์ˆ˜ ์žˆ๋‹ค.

@Test
void equalTest() throws Exception{
Item item1 = new Item("item1");
Item item2 = new Item("item1");
// 1. ๊ทธ๋ƒฅ ์‚ฌ์šฉ
assertThat(item1).isEqualTo(item2);
// 2. unsingComparator๋ฅผ ์ด์šฉํ•ด์„œ ๋น„๊ตํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ง์ ‘ ๊ตฌํ˜„
assertThat(item1)
.usingComparator(new Comparator<Item>() {
@Override
public int compare(Item o1, Item o2) {
return o1.getName().compareTo(o2.getName());
}
})
.isEqualTo(item2);
// 3. ์ต๋ช… ๊ฐ์ฒด๋Œ€์‹  ๋žŒ๋‹ค์‹์„ ์“ฐ๋ฉด ์ข€ ๋” ๊ฐ„๊ฒฐํ•˜๋‹ค.
assertThat(item1)
.usingComparator((a, b) -> a.getName().compareTo(b.getName()))
.isEqualTo(item2);
}

์ปฌ๋ ‰์…˜์—์„œ ์‚ฌ์šฉ๋˜๋Š” Comparator๋„ ์ปค์Šคํ…€ ํ•  ์ˆ˜ ์žˆ๊ธดํ•œ๋ฐ... ์‚ฌ์šฉํ•  ์ผ์€ ๊ฑฐ์˜ ์—†๋‹ค.

assertThat(itemList).contains(item1, item2).doesNotContain(item3);
assertThat(itemList) // ์ด๋•Œ๋Š” usingElementComparator ์‚ฌ์šฉ
.usingElementComparator((a, b) -> a.getName().compareTo(b.getName()))
.contains(item1, item2, item3); // ํŒŒ๋ผ๋ฏธํ„ฐ b ์— ๋„ฃ์„ ๊ฐ’๋“ค. 3๊ฐœ๋‹ˆ๊นŒ ์ด 3๋ฒˆ ๋น„๊ต

 

 

@ ๊ฐ์ฒด๊ฐ„์˜ ๋น„๊ต(ํŠน์ • ํ•„๋“œ๊ฐ’ ๋น„๊ต), usingComparsion ์‚ฌ์šฉํ•˜๊ธฐ

๊ธฐ์กด์—๋Š” isEqualToComparingFieldByFieldRecursively(...) ์ด๋Ÿฐ๊ฑธ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ, 2020๋…„ 8์›” 3.17๋ฒ„์ „์— ์ „๋ถ€ Deprecated ๋˜์—ˆ๋‹ค. 

๊ฐ€๋…์„ฑ ๊ฐœํŒ์ด๋ผ์„œ ์–ด์ฐจํ”ผ ์•ˆ์“ฐ๋Š” ๊ธฐ๋Šฅ์ด์—ˆ๋Š”๋ฐ, ์—ญ์‹œ ๋‚˜๋งŒ ๊ทธ๋Ÿฐ๊ฒŒ ์•„๋‹ˆ์—ˆ๋„ค, ๋„ˆ๋ฌด์ข‹์•„!

 ์ฐธ๊ณ ๋กœ 2022.01 ๊ธฐ์ค€ ์ตœ์‹ ๋ฒ„์ „์€ 3.22

 

Deprecated๋œ ๋ฉ”์„œ๋“œ ์„ค๋ช… ๊ตฌ๊ฒฝํ•˜๊ธฐ

๋”๋ณด๊ธฐ

assertThat.isEqualTo๊ฐ€ ๋’ค์— 'To'๋ฅผ ๊ตณ์ด ๋ถ™์ธ ์ด์œ ๊ฐ€ ์—ฌ๊ธฐ์„œ ๋‚˜์˜จ๋‹ค.

@Test
void comparison() throws Exception{
Item item1 = new Item("item1", 1000);
Item item2 = new Item("item1", 1000);
Item item3 = new Item("item1", 3000);
// 1 ๊ฐ๊ฐ์˜ ํ•„๋“œ ๊ฐ’์„ ๋น„๊ตํ•œ๋‹ค.
assertThat(item1).isEqualToComparingFieldByField(item2);
assertThat(item1).isEqualToComparingFieldByField(item3) โžก price๊ฐ€ ๋‹ฌ๋ผ์„œ ์‹คํŒจ
// 2 ์ฃผ์–ด์ง„ "name" ํ•„๋“œ๋งŒ ๋น„๊ตํ•œ๋‹ค
assertThat(item1).isEqualToComparingOnlyGivenFields(item2, "name");
assertThat(item1).isEqualToComparingOnlyGivenFields(item3, "name");
// 3 "name"ํ•„๋“œ๋ฅผ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€ ํ•„๋“œ๋ฅผ ๋น„๊ตํ•œ๋‹ค.
assertThat(item1).isEqualToIgnoringGivenFields(item2, "price");
assertThat(item1).isEqualToIgnoringGivenFields(item3, "price");
// 4 null์ธ ํ•„๋“œ๊ฐ’์„ ์ œ์™ธํ•˜๊ณ  ๋‚˜๋จธ์ง€๋ฅผ ๋น„๊ตํ•œ๋‹ค.
Item item4 = new Item(null, 1000);
assertThat(item1).isEqualToIgnoringNullFields(item4);
}

๋‹ค๋งŒ ํŒŒ๋ผ๋ฏธํ„ฐ์— ๊ฐ์ฒด๊ฐ€ ์ฃผ์–ด์ง„๋‹ค๋ฉด ๋‹จ์ˆœํžˆ ๊ฐ์ฒด๋ฅผ .isEqual()๋กœ ๋น„๊ตํ•ด๋ฒ„๋ ค์„œ ๋™์ผ์„ฑ, ์ฆ‰ ์ฐธ์กฐ๊ฐ’์„ ๋น„๊ตํ•˜๊ฒŒ ๋œ๋‹ค.

โžก ๊ฐ์ฒด์˜ ๋™๋“ฑ์„ฑ์„ ๋น„๊ตํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด XXXRecursively()๋ฅผ ์ด์šฉํ•˜๋ฉด ๋œ๋‹ค.

@Test
void recursively() throws Exception{
Person person1 = new Person("Dexter");
Person person2 = new Person("Dexter");
Item item1 = new Item("item1", 1000, person1);
Item item2 = new Item("item1", 1000, person2);
// ํ…Œ์ŠคํŠธ ์‹คํŒจ
// assertThat(item1).isEqualToComparingFieldByField(item2);
// ๋™๋“ฑ์„ฑ์„ ๋น„๊ตํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ฐ์ฒด๊ฐ€ ๊ฐ€์ง„ ํ•„๋“œ๋ฅผ ์žฌ๊ท€์ ์œผ๋กœ ํŒŒ๊ณ ๋“ค์–ด์„œ ๋น„๊ตํ•œ๋‹ค.
assertThat(item1).isEqualToComparingFieldByFieldRecursively(item2);
}

์œ„์˜ ๊ธฐ๋Šฅ๋“ค์€ ์ „๋ถ€ Deprecated ๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ์™€ ์ตœ๊ณ ๋‹ค ์ตœ๊ณ !

 

์ด์ œ ๊ฐ์ฒด๊ฐ„์˜ ๋น„๊ต๋Š” usingRecursiveComparison() ๊ณผ ignoringFields...()๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค!! ์™€!

๋”ฑํžˆ ์„ค๋ช…์ด ํ•„์š”์—†์„ ์ •๋„๋กœ, ๊ฐ€๋…์„ฑ์ด ์ •๋ง ๋งŽ์ด ์ข‹์•„์กŒ์Šต๋‹ˆ๋‹ค.

Assertions.assertThat(objActual)
.usingRecursiveComparison() // ์žฌ๊ท€์ ์œผ๋กœ ๊ฐ’์„ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.
.ignoringFields("uniqueId", "otherId") // objActual์— ํ•ด๋‹น ํ•„๋“œ๋Š” ๋ฌด์‹œํ•ฉ๋‹ˆ๋‹ค.
.ignoringFieldsMatchingRegexes(".*someId") // ํ•„๋“œ์ด๋ฆ„์ด ํ•ด๋‹น ์ •๊ทœํ‘œํ˜„์‹์ด๋ฉด ๋ฌด์‹œํ•ฉ๋‹ˆ๋‹ค.
.ignoringOverriddenEqualsForTypes(MyData.class, MyDataItem.class) // ๐Ÿงจ ์•„๋ž˜์— ์ฃผ์˜์‚ฌํ•ญ ์–ธ๊ธ‰
.isEqualTo(objExpected);
// equals not overridden in TolkienCharacter
TolkienCharacter frodo = new TolkienCharacter("Frodo", 33, HOBBIT);
TolkienCharacter frodoClone = new TolkienCharacter("Frodo", 33, HOBBIT);
TolkienCharacter young = new TolkienCharacter("Frodo", 22, HOBBIT);
// ๋‹จ์ˆœํžˆ isEqualTo๋กœ ๋น„๊ตํ•˜๊ฒŒ๋˜๋ฉด, ๊ฐ’์ด ์•„๋‹Œ ์ฐธ์กฐ๊ฐ’์„ ๋น„๊ตํ•˜๊ฒŒ๋œ๋‹ค
assertThat(frodo).isNotEqualTo(frodoClone);
// ๋‘ ๊ฐ์ฒด์˜ ๊ฐ’์ด ๊ฐ™๋‹ค.
assertThat(frodo).usingRecursiveComparison()
.isEqualTo(frodoClone);
// ๋‘๊ฐ์ฒด์˜ ๊ฐ’์ด ๋‹ค๋ฅด๋‹ค.
assertThat(frodo).usingRecursiveComparison()
.isNotEqualTo(young);

 

 

๐Ÿงจ ์ฃผ์˜์‚ฌํ•ญ

์ตœ์‹ ๋ฒ„์ „(3.17.0์ด์ƒ)์—์„œ๋Š” ๋น„๊ตํ•  ๋•Œ ์‚ฌ์šฉ์ž๊ฐ€ ์žฌ์ •์˜ํ•œ Equals ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. (๊ทธ๋ƒฅ ํ•„๋“œ๋น„๊ตํ•จ)

ํ•˜์ง€๋งŒ ์ด์ „๋ฒ„์ „์—์„œ๋Š” Equals๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ๊ธฐ๋ณธ์ด๋ผ์„œ, ๋ฒ„์ „์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒŒ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ์–ด์š”.

public static class Address {
int number;
String street;
// ์ˆซ์ž๋งŒ ๋น„๊ตํ•˜๋„๋ก equals๋ฅผ ์žฌ์ •์˜
@Override
public boolean equals(final Object other) {
if (!(other instanceof Address)) return false;
Address castOther = (Address) other;
return Objects.equals(number, castOther.number);
}
}
Person sherlock = new Person("Sherlock", 1.80);
sherlock.home.address.street = "AAA Street";
sherlock.home.address.number = 221;
Person sherlock2 = new Person("Sherlock", 1.80);
sherlock2.home.address.street = "BBB Street";
sherlock2.home.address.number = 221;
// ๊ฐ์ฒด ๋‚ด๋ถ€์˜ Equals๋ฅผ ์‚ฌ์šฉํ•˜๋Š”์ง€, ๊ทธ๋ƒฅ ๋น„๊ตํ•˜๋Š”์ง€ ๋ชจ๋ฆ…๋‹ˆ๋‹ค. ๋ฒ„์ „๋งˆ๋‹ค ๋‹ค๋ฆ„..
assertThat(sherlock).usingRecursiveComparison()
.isEqualTo(sherlock2); // ๋ฒ„์ „๋งˆ๋‹ค ๊ฒ€์ฆ๊ฒฐ๊ณผ๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค...๋ญ๋ผ๊ณ ?

 

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. ignoringOverriddenEquals...() ๋ฅผ ์ด์šฉํ•ด์„œ ๊ตฌ๋ฒ„์ „์—์„œ๋„ Equals๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋„๋ก ๊ฐ•์ œํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๊ฐ€๋…์„ฑ๋„ ์˜ฌ๋ผ๊ฐ€๋Š”๊ฑด ๋ค์ด๊ตฌ์š”.

assertThat(sherlock).usingRecursiveComparison()
.ignoringOverriddenEquals() // ๋ช…์‹œ์ ์œผ๋กœ Equals๋ฅผ ๋ฌด์‹œํ•จ!
.isEqualTo(sherlock2);
// ๋งŒ์•ฝ ํŠน์ •๊ฐ’๋งŒ ๋ฌด์‹œํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ์œ„์—์„œ ๋งํ•œ ..Types, ..Fields, ..Regexes๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
assertThat(sherlock).usingRecursiveComparison()
.ignoringOverriddenEqualsForTypes(Address.class)
.isEqualTo(sherlock2);

 

 

 

1. ๋ณต์žกํ•œ ํฌํ•จ ๊ด€๊ณ„(is A)์ผ๋•Œ

๊ฐ™์€ ๊ฐ์ฒด๊ฐ„์˜ ๊ฐ’ ๋น„๊ต๋ผ๋ฉด, ๋”ฑํžˆ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ์•„๋ž˜์™€ ๊ฐ™์ด ๋ณต์žกํ•œ Person ๊ฐ์ฒด๊ฐ€ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ด๋ณด์ž. Person.Home.Address ๋ผ๋Š” ๋ณต์žกํ•œ ๊ด€๊ณ„์ด๋‹ค.

class Person {
String name;
double height;
Home home = new Home();
public Person(String name, double height) {
this.name = name;
this.height = height;
}
}
class Home {
Address address = new Address();
Date ownedSince;
public static class Address {
int number;
String street;
}
}

 

Recursive๋Š” ๊ฐ ๊ฐ์ฒด์˜ ๊ฐ’์„ ์žฌ๊ท€์ ์œผ๋กœ ๊ฒ€์‚ฌํ•œ๋‹ค. ์ฆ‰, ๊ทธ๋ƒฅ ์“ฐ๋ฉด ๋œ๋‹ค. ์•„๋ž˜๋Š” ์„ฑ๊ณตํ•˜๋Š” ํ…Œ์ŠคํŠธ์˜ˆ์ œ์ด๋‹ค.
์‹ฌ์ง€์–ด๋Š” Person { Person myPerson } ์ด๋Ÿฐ ์žฌ๊ท€์ ์ธ ํฌํ•จ๊ด€๊ณ„๋„ ๋น„๊ต๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

๐Ÿงจ usingRecursive๋Š” ๊ฐ์ฒด์˜ Equals๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. Equals๋กœ ๋น„๊ตํ•˜๊ณ ์‹ถ๋‹ค๋ฉด usingOverridenEquals๋ฅผ ์“ฐ์ž

@Test
void myTest() throws Exception {
Person sherlock = new Person("Sherlock", 1.80);
sherlock.home.ownedSince = new Date(123); // Person.Home
sherlock.home.address.street = "Baker Street"; // Person.Home.Address
sherlock.home.address.number = 221; // Person.Home.Address
Person sherlock2 = new Person("Sherlock", 1.80);
sherlock2.home.ownedSince = new Date(123); // Person.Home
sherlock2.home.address.street = "Baker Street"; // Person.Home.Address
sherlock2.home.address.number = 221; // Person.Home.Address
// ์ด๋ ‡๊ฒŒ ๋น„๊ตํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฐธ๊ณ ๋กœ ๋น„๊ตํ•  ๋•Œ ๊ฐ์ฒด์˜ Equals ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.
assertThat(sherlock).usingRecursiveComparison()
.isEqualTo(sherlock2);
// ์ฐธ๊ณ ๋กœ isEqualTo๋กœ ๋น„๊ตํ•˜๋ฉด, ์ฐธ์กฐ๊ฐ’์„ ๋น„๊ตํ•˜๋Š”๊ฑฐ๋ผ ์‹คํŒจํ•œ๋‹ค.
assertThat(sherlock).isNotEqualTo(sherlock2);
}

 

 

2. ํŠน์ • ํ•„๋“œ๊ฐ’( ๋˜๋Š” ํŠน์ • ํƒ€์ž…)์€ ๋ฌด์‹œํ•˜๊ณ  ์‹ถ์„ ๋•Œ

ignoringFields...๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. ๊ฐ€๋…์„ฑ์ด ์ข‹์•„์„œ ์„ค๋ช…์ด ์—†์–ด๋„ ์‰ฝ๊ฒŒ ์•Œ ์ˆ˜ ์žˆ์„๋“ฏ

Person sherlock = new Person("Sherlock", 1.80); // Person์˜ ์ด๋ฆ„์ด ๋‹ค๋ฅด๋‹ค.
sherlock.home.address.street = "Baker Street"; // Person.Home.Address ์˜ street๊ฐ€ ๋‹ค๋ฅด๋‹ค.
sherlock.home.address.number = 221;
Person moriarty = new Person("Moriarty", 1.80);
moriarty.home.address.street = "Crime Street";
moriarty.home.address.number = 221;

 

์žฌ๊ท€์ ์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค๋Š” ๊ฑธ ์ดํ•ดํ•˜๋ฉด, ์‰ฝ๊ฒŒ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ์•„๋ž˜์˜ ํ•„๋“œ๋ช…์€ Person.Home.Address.street ๋ฅผ ๋ฌด์‹œํ•œ๋‹ค.

["home.address.street", "home.adress", "home"]

// ํ•„๋“œ ์ด๋ฆ„์ค‘ "name" ๊ณผ "home.address.street" ๊ฐ€ ๋ฌด์‹œ๋œ๋‹ค.
assertThat(sherlock).usingRecursiveComparison()
.ignoringFields("name", "home.address.street")
.isEqualTo(moriarty);
// ์ฐธ๊ณ ๋กœ "home" ์ด๋ ‡๊ฒŒ ์ ์œผ๋ฉด, "home" ์•„๋ž˜์˜ ๋ชจ๋“  ํ•„๋“œ๊ฐ€ ๋ฌด์‹œ๋œ๋‹ค.(์žฌ๊ท€์ ์œผ๋กœ ๋™์ž‘ํ•˜๋‹ˆ๊นŒ)
assertThat(sherlock).usingRecursiveComparison()
.ignoringFields("name", "home")
.isEqualTo(moriarty);
// ํ•„๋“œ๋ช…์„ ์ •๊ทœํ‘œํ˜„์‹์œผ๋กœ ๊ฑฐ๋ฅผ ์ˆ˜ ์žˆ๋‹ค. (.*ome) ๋Š” ๋๋ถ€๋ถ„์ด ome๋กœ ๋๋‚˜๋Š” ๊ฒฝ์šฐ.
assertThat(sherlock).usingRecursiveComparison()
.ignoringFieldsMatchingRegexes(".*ome") // home, aome, abome...
.isEqualTo(moriarty);

 

์ด๋ฆ„์ด ์•„๋‹Œ ํƒ€์ž…์œผ๋กœ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค. ์ด ๊ฒฝ์šฐ ์žฌ๊ท€์ ์œผ๋กœ ํ•ด๋‹น ํƒ€์ž…์„ ๋‹ค ๋ฌด์‹œํ•œ๋‹ค.

Person tallSherlock = new Person("sherlock", 2.10);
tallSherlock.home.address.street = "Long Baker Street";
tallSherlock.home.address.number = 222;
// Double, Address ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๋Š” "height" ์™€ "address" ํ•„๋“œ๊ฐ€ ๋ฌด์‹œ๋œ๋‹ค.
assertThat(sherlock).usingRecursiveComparison()
.ignoringFieldsOfTypes(double.class, Address.class)
.isEqualTo(tallSherlock);

 


3. null ์ธ ํ•„๋“œ๊ฐ’๋งŒ ๋ฌด์‹œํ•˜๊ณ  ์‹ถ์„ ๋•Œ

๊ฒ€์ฆ๋Œ€์ƒ์— null์ธ ํ•„๋“œ ๊ฐ’์„ ๋ฌด์‹œํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ignoringActualNullFields()๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

@Test
void myTest() throws Exception {
Person noName = new Person(null, 1.80);
noName.home.address.street = null;
noName.home.address.number = 221;
Person sherlock = new Person("Sherlock", 1.80);
sherlock.home.address.street = "Baker Street";
sherlock.home.address.number = 221;
// ํ…Œ์ŠคํŠธ ์„ฑ๊ณต. name ๊ณผ home.address.street ํ•„๋“œ๊ฐ€ ๋ฌด์‹œ๋จ.
assertThat(noName).usingRecursiveComparison()
.ignoringActualNullFields()
.isEqualTo(sherlock);
// ํ…Œ์ŠคํŠธ ์‹คํŒจ. ๊ฒ€์ฆ๋Œ€์ƒ์—๋Š” null ํ•„๋“œ๊ฐ€ ์—†์–ด์„œ, ์ œ์™ธํ•  ํ•„๋“œ๊ฐ€ ์—†์Œ.
assertThat(sherlock).usingRecursiveComparison()
.ignoringActualNullFields()
.isEqualTo(noName); // AssertionError
}

 

assertThat(๊ฒ€์ฆ๋Œ€์ƒ)์ด ์•„๋‹ˆ๋ผ, ๋น„๊ตํ•  ๊ฐ์ฒด์˜ null ์ด ๋“ค์–ด๊ฐ„ ํ•„๋“œ๋ฅผ ์ œ์™ธํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ExpectedNullFields๋ฅผ ์‚ฌ์šฉํ•˜์ž.

๋ณดํ†ต ํ…Œ์ŠคํŠธ์šฉ ๊ฐ์ฒด๋ฅผ ๋Œ€์ถฉ๋งŒ๋“œ๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„์„œ, ์œ ์šฉํ•˜๊ฒŒ ์“ธ ์ˆ˜ ์žˆ๋‹ค.

Person sherlock = new Person("Sherlock", 1.80); // Person(String name, Double height)
sherlock.home.address.street = "Baker Street";
sherlock.home.address.number = 221;
// ๋น„๊ตํ•  ํ…Œ์ŠคํŠธ์šฉ ๋ฐ์ดํ„ฐ ์ •์˜
Person testData = new Person(null, 1.80);
noName.home.address.street = null; // String์˜ ๊ธฐ๋ณธ๊ฐ’์€ null ์ด๋ผ์„œ, ์ด๊ฒƒ๋„ ์ƒ๋žต๊ฐ€๋Šฅ
noName.home.address.number = 221;
// sherlock ๊ฒ€์ฆ ์„ฑ๊ณต, ๋น„๊ต๋Œ€์ƒ(testData)์—์„œ null ์ธ ํ•„๋“œ๋Š” ๋ฌด์‹œํ•˜๊ณ  ๋น„๊ตํ•œ๋‹ค.
assertThat(sherlock).usingRecursiveComparison()
.ignoringExpectedNullFields()
.isEqualTo( testData );
// testData ๊ฒ€์ฆ ์‹คํŒจ. ๋น„๊ต๋Œ€์ƒ์ธ (sherlock)์—๋Š” ์žˆ๋Š” ํ•„๋“œ๊ฐ€, testData์—๋Š” null์ด๊ธฐ ๋•Œ๋ฌธ
assertThat(testData).usingRecursiveComparison()
.ignoringExpectedNullFields()
.isEqualTo( sherlock ); // AssertionError ๋ฐœ์ƒ

 

4. Optional.empty ํ•„๋“œ ๋ฌด์‹œ

๊ฐ์ฒด๊ฐ€ Optional ํ•„๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์„ ์ผ์ด ์—†๊ธฐ์— ๊ฑฐ์˜ ์“ธ ์ผ์€ ์—†๋‹ค.

null ๋ฌด์‹œ์™€ ์‚ฌ์šฉ๋ฒ•์€ ๋™์ผํ•˜๋‹ค. ๊ฒ€์ฆ๋Œ€์ƒ์— Optional.empty๊ฐ€ ์žˆ๋‹ค๋ฉด ํ•ด๋‹น ํ•„๋“œ๋Š” ๋ฌด์‹œํ•˜๊ณ  ๋น„๊ตํ•œ๋‹ค.

var OptionalEmptyHome = new Home("kim", "busan", Optional.empty);
var OptionalHome = new Home("kim", "busan", Optional.of(30));
// ํ…Œ์ŠคํŠธ ์„ฑ๊ณต. OptioanlEmptyHome์˜ Optional.empty ๊ฐ’์€ ๋ฌด์‹œํ•œ๋‹ค.
assertThat(OptionalEmptyHome).usingRecursiveComparison()
.ignoringActualEmptyOptionalFields()
.isEqualTo(OptionalHome);
// ํ…Œ์ŠคํŠธ ์‹คํŒจ. OptionalHome์˜ empty๋Š” ๋ฌด์‹œํ• ๋ ค๊ณ  ํ–ˆ์œผ๋‚˜, empty๊ฐ€ ์—†์Œ.
assertThat(OptionalHome).usingRecursiveComparison()
.ignoringActualEmptyOptionalFields()
.isEqualTo(OptionalEmptyHome); // AssertError

 

 

5. ๋น„๊ต์—ฐ์‚ฐ์„ ์ปค์Šคํ…€ํ•˜๊ณ  ์‹ถ์„ ๋•Œ

3.1.7์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ์ •์˜ํ•œ Equals๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , ํ•„๋“œ๋ฅผ ๋น„๊ตํ•œ๋‹ค๊ณ  ํ–ˆ๋‹ค.

๊ทผ๋ฐ ์›ํ•œ๋‹ค๋ฉด ๋‚ด๊ฐ€ ์ง์ ‘ ๋น„๊ตํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์ˆ˜๋„ ์žˆ๋‹ค. ์‚ฌ์šฉ๋ฒ•์€ ๊ฐ„๋‹จํ•˜๋‹ค. .withEqualsFor...( ํ•จ์ˆ˜, ๋Œ€์ƒ ) 

@Test
void myTest() throws Exception {
TolkienCharacter frodo = new TolkienCharacter("Frodo", 1.2);
TolkienCharacter tallerFrodo = new TolkienCharacter("Frodo", 1.3);
TolkienCharacter reallyTallFrodo = new TolkienCharacter("Frodo", 1.9);
BiPredicate<Double, Double> myCompare = (d1, d2) -> Math.abs(d1 - d2) <= 0.5;
// == Comparator<Double> myCompare = (d1, d2) -> Math.abs(d1 - d2) <= 0.5 ? 0 : 1;
// ํŠน์ • ํ•„๋“œ์˜ ์ปค์Šคํ…€ ๋น„๊ต
assertThat(frodo).usingRecursiveComparison()
.withEqualsForFields(myCompare, "height")
.isEqualTo(tallerFrodo);
// ํŠน์ • ํƒ€์ž…์˜ ์ปค์Šคํ…€ ๋น„๊ต
assertThat(frodo).usingRecursiveComparison()
.withEqualsForType(myCompare, Double.class)
.isEqualTo(tallerFrodo);
}

 

์ฐธ๊ณ ๋กœ ์˜ˆ์™ธ๊ฐ€ ํ„ฐ์กŒ์„ ๋•Œ ๋ฉ”์‹œ์ง€๋„ ์ปค์Šคํ…€ ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ณ„๋กœ ์“ธ๋ชจ๋Š” ์—†์ง€๋งŒ

// ๋ฌผ๋ก  ๊ฒ€์ฆ์— ์‹คํŒจํ•ด์•ผ ๋œฌ๋‹ค.
assertThat(frodo).usingRecursiveComparison()
.withErrorMessageForFields("๐Ÿงจheight ํ•„๋“œ ๋น„๊ต์—์„œ ์˜ˆ์™ธํ„ฐ์กŒ๋‹ค๋ฆฌ", "height")
// .withErrorMessageForType(message, Double.class) ์ด๋Ÿฐ๊ฑฐ๋„ ๊ฐ€๋Šฅ
.isEqualTo(tallerFrodo);

์•„๋ž˜์™€ ๊ฐ™์ด ๊ฒ€์ฆ ์‹คํŒจ์‹œ, ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๊ฐ€ ๋ณ€๊ฒฝ ๋œ๋‹ค..ใ…‹ใ…‹ String ๊ฐ’์ด๋‹ˆ, ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ํ•„๋“œ๋ฅผ ๋ณด์—ฌ์ค„ ์ˆ˜๋„ ์žˆ์„๋“ฏ

Expecting actual:
TolkienCharacter [name=Frodo, height=1.2]
to be equal to:
TolkienCharacter [name=Frodon, height=1.4]
when recursively comparing field by field, but found the following 2 differences:
// ์ฐธ๊ณ ๋กœ ๊ธฐ๋ณธ ๋ฉ”์‹œ์ง€๋Š” ์ด๋Ÿฌํ•˜๋‹ค
field/property 'name' differ:
- actual value : "Frodo"
- expected value: "Frodon"
// ๋‚ด๊ฐ€ ์ปค์Šคํ…€ํ•œ ๋ฉ”์‹œ์ง€๊ฐ€ ๋œฌ๋‹ค. ๊ธฐ๋ณธ๋ฉ”์‹œ์ง€๋Š” ์—†์–ด์ง..ใ…‹ใ…‹
๐Ÿงจheight ํ•„๋“œ ๋น„๊ต์—์„œ ์˜ˆ์™ธํ„ฐ์กŒ๋‹ค๋ฆฌ
The recursive comparison was performed with this configuration:
- no overridden equals methods were used in the comparison (except for java types)
- these types were compared with the following comparators:
- java.lang.Double -> DoubleComparator[precision=1.0E-15]
- java.lang.Float -> FloatComparator[precision=1.0E-6]
- java.nio.file.Path -> lexicographic comparator (Path natural order)
- actual and expected objects and their fields were compared field by field recursively even if they were not of the same type, this allows for example to compare a Person to a PersonDto (call strictTypeChecking(true) to change that behavior).
- these fields had overridden error messages:
- height

 

 


@ ๋‹ค๋ฅธ ๊ฐ์ฒด๊ฐ„ ๋น„๊ต

RecursiveComparsionConfiguraion ์„ค์ •๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๊ณ 

usingRecursiveFieldByFieldElementComparator ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. ์ข€ ๋ณต์žกํ•˜๊ธดํ•˜๋‹ค ใ…‹ใ…‹

var configuration = RecursiveComparisonConfiguration.builder()
.withIgnoredFields("fieldname") // ์ด๋ ‡๊ฒŒ ์ปค์Šคํ…€
.build();
assertThat(...).usingRecursiveFieldByFieldElementComparator(configuration)
.contains(...);
Doctor DoctorA = new Doctor("A", true); // Doctor(String name, boolean isHuman)
Doctor DoctorB = new Doctor("B", true);
Doctor DoctorC = new Doctor("C", true);
Person PersonA = new Person("A", false); // Person(String name, boolean isHuman)
Person PersonB = new Person("B", false);
Person PersonC = new Person("C", false);
List<Doctor> doctors = List.of(drSheldon, drLeonard, drRaj);
var configuration = RecursiveComparisonConfiguration.builder()
.withIgnoredFields("isHuman")
.build();
// ํ…Œ์ŠคํŠธ ์„ฑ๊ณต. Doctor๊ฐ์ฒด์™€ Person ๊ฐ์ฒด์˜ ํ•„๋“œ์ด๋ฆ„์„ ๊ธฐ์ค€์œผ๋กœ ๋น„๊ตํ•œ๋‹ค.
assertThat(doctors).usingRecursiveFieldByFieldElementComparator(configuration)
.contains(personA);
// ํ…Œ์ŠคํŠธ ์‹คํŒจ. ํ•ด๋‹น ์ด๋ฆ„์€ ๋ฆฌ์ŠคํŠธ์— ์—†๋‹ค.
personA.setName("DDD");
assertThat(doctors).usingRecursiveFieldByFieldElementComparator(configuration)
.contains(personA); // AssertionError
๋ธ”๋กœ๊ทธ์˜ ํ”„๋กœํ•„ ์‚ฌ์ง„

๋ธ”๋กœ๊ทธ์˜ ์ •๋ณด

JiwonDev

JiwonDev

ํ™œ๋™ํ•˜๊ธฐ