์๋ฐ ํ ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ - 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 ๋์๋ค.
๊ฐ๋ ์ฑ ๊ฐํ์ด๋ผ์ ์ด์ฐจํผ ์์ฐ๋ ๊ธฐ๋ฅ์ด์๋๋ฐ, ์ญ์ ๋๋ง ๊ทธ๋ฐ๊ฒ ์๋์๋ค, ๋๋ฌด์ข์!

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