์๋ฐ ํ ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ - 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
'๐ฑ Spring Framework > Test (JUnit, SpringBootTest)' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
ํ ์คํธ ๋ฐ์ดํฐ๋๊ตฌ - Fixture Monkey (3) | 2022.04.02 |
---|---|
์ข์ ํ ์คํธ๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ (5) | 2022.03.08 |
spring security ํ ์คํธ์ฉ ์ค์ ๋ฐฉ๋ฒ (1) | 2022.03.07 |
Spring Boot Test์ ์ฌ๋ผ์ด์ค ํ ์คํธ (0) | 2021.09.08 |
๋ค์ํ ํ ์คํธ์ Test Double (+Mockist) (0) | 2021.09.07 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev