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

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