우아한테크코스 레벨 1 자료를 참고하여 학습한 내용을 정리한 글입니다.
💭 들어가며
테스트 코드를 작성하는 것은 개발자에게 매우 중요한 일이다. 경험이 쌓이고 연차가 늘어날수록 코드의 안정성을 더욱 중요하게 고려해야 한다. 테스트 코드를 작성할 때 일반적으로 org.junit.jupiter.api.Assertions(JUnit) 또는 org.assertj.core.api.Assertions(AssertJ)를 사용하며, 대부분 후자인 AssertJ를 선호하는 경우가 많다. 그 이유는 아래에 살펴보자.
✅ Test Annotation
Annotation | 설명 |
@Test | 단위 테스트를 정의 |
@DisplayName | 테스트 이름을 지정 |
@Nested |
내부 클래스를 이용한 그룹화 테스트 |
@Disabled | 테스트를 비활성화 |
@ParameterizedTest | 여러 개의 입력값으로 테스트 수행 |
@ValueSource | 단일 값 배열을 입력값으로 제공 |
@MethodSource | 외부 메서드에서 제공하는 데이터를 입력값으로 사용 |
@RepeatedTest | 테스트를 여러 번 반복 실행 |
@BeforeAll | 모든 테스트 실행 전에 한 번 실행 |
@AfterAll | 모든 테스트 실행 후 한 번 실행 |
@BeforeEach | 각 테스트 실행 전에 실행 |
@AfterEach | 각 테스트 실행 후 실행 |
🔽 @Test, @DisplayName
@Test
@DisplayName("@DisplayName 어노테이션으로 테스트의 이름을 작성한다")
void test() {
// 테스트 내용
}
🔽 @Nested
@Nested
@DisplayName("@Nested 애노테이션 학습 테스트")
class NestedAnnotationTest {
@Test
@DisplayName("@Nested 애노테이션을 붙여줘야 중첩 클래스로서의 역할을 수행한다")
void test() {
// 테스트 내용
}
}
🔽 @Disabled
@Nested
@DisplayName("@Disabled 애노테이션 학습 테스트")
class DisabledAnnotationTest {
@Test
@Disabled
@DisplayName("@Disabled 애노테이션을 붙여줘야 테스트 메서드로서의 역할을 수행하지 않는다")
void test() {
throw new RuntimeException("항상 실패한다.");
}
}
🔽 @ParameterizedTest, @ValueSource
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4})
@DisplayName("ValueSource 애노테이션을 붙여 정수 매개변수를 여러 번 입력받는다")
void test(int value) {
assertTrue(value > 0);
}
@ParameterizedTest
@ValueSource(strings = {"a", "b", "c"})
@DisplayName("ValueSource 애노테이션을 붙여 문자열 매개변수를 여러 번 입력받는다")
void test(String value) {
assertEquals(value.length(), 1);
}
🔽 @ParameterizedTest, @MethodSource
@ParameterizedTest
@MethodSource("methodSourceIterableTestArguments")
@DisplayName("MethodSource 애노테이션을 붙여 Iterable 매개변수를 입력받는다")
void test(List<Integer> numbers) {
assertEquals(numbers.size(), 3);
}
private static Stream<Arguments> methodSourceIterableTestArguments() {
return Stream.of(
Arguments.arguments(List.of(1, 4, 5)),
Arguments.arguments(List.of(1, 2, 3)),
Arguments.arguments(List.of(1, 3, 4))
);
}
✅ JUnit
▶ JUnit 5 메서드
메서드 | 설명 |
assertEquals(a, b) | 두 값이 같은지 검증 |
assertNotEquals(a, b) | 두 값이 다른지 검증 |
assertSame(a, b) | 두 객체가 같은지 검증 |
assertNotSame(a, b) | 두 객체가 서로 다른지 검증 |
assertThrows(Exception.class, () -> {...}) | 특정 예외가 발생하는지 검증 |
assertDoesNotThrow(() -> {...}) | 예외가 발생하지 않는지 검증 |
assertAll(() -> {...}, () -> {...}) | 여러 개의 검증을 한 번에 실행 |
assertTrue(condition) | 조건이 참인지 검증 |
assertFalse(condition) | 조건이 거짓인지 검증 |
이 외에도 다양한 메서드가 제공된다.
🔽 assertEquals
@Test
@DisplayName("assertEquals 메서드로 두 값이 같은지 비교한다")
void test() {
final var actual = 1 + 2;
final var expected = 3;
assertEquals(actual, expected);
}
🔽 assertNotEquals
@Test
@DisplayName("assertNotEquals 메서드로 두 값이 다른지 비교한다")
void test() {
final var actual = 1 + 2;
final var unexpected = 0;
assertNotEquals(actual, unexpected);
}
🔽 assertSame
@Test
@DisplayName("assertSame 메서드로 두 객체가 같은지 비교한다")
void test() {
final var object = new Object();
final var actual = object;
final var expected = object;
assertSame(actual, expected);
}
🔽 assertThrows
@Test
@DisplayName("assertThrows 메서드로 특정 예외가 발생하는지 비교한다")
void test() {
assertThrows(IllegalCallerException.class, this::causeException);
}
private void causeException() {
throw new IllegalCallerException("예외가 발생했습니다.");
}
🔽 assertDoesNotThrow
@Test
@DisplayName("assertDoesNotThrow 메서드로 특정 예외가 발생하지 않는 것을 명시한다")
void test() {
assertDoesNotThrow(() -> {
final var number = Integer.valueOf(0x80000000);
});
}
🔽 assertAll
@Test
@DisplayName("assertAll 메서드로 여러 검증 코드를 한 번에 실행한다")
void test() {
assertAll(
() -> assertEquals(3, 1 + 2),
() -> assertEquals(5, 3 + 2),
() -> assertEquals(21, 7 * 3),
() -> assertEquals(16, 3 * 7 ^ 5),
() -> assertEquals(5, 7 * 3 / 5 + 33 / 21),
() -> assertEquals(22, 33 * 3 / 5 + 7 / 2),
() -> assertEquals(23, 33 * 3 / 5 + 7 / 2 + 1)
);
}
✅ AssertJ
▶ AssertJ를 사용하는 이유
AssertJ는 서드파티 라이브러리로, JUnit보다 가독성이 향상된 검증 기능을 제공한다. 실제로 JUnit 공식 문서에서도 AssertJ 사용을 권장하고 있다.
서드파티 라이브러리(Third-Party Library)란, 특정 프로그래밍 언어나 프레임워크의 공식 라이브러리가 아닌 외부 개발자나 기업이 제공하는 라이브러리를 의미한다. 예를 들어, JUnit은 공식 테스트 프레임워크이지만, AssertJ는 JUnit의 검증 기능을 확장한 서드파티 라이브러리이다.
🔽 AssertJ의 장점
- 가독성
- 자세한 실패 메시지
- 메서드 체이닝 지원
▶ AssertJ 메서드
메서드 | 설명 |
assertThat(a).isEqualTo(b) | 두 값이 같은지 검증 |
assertThat(a).isNotEqualTo(b) | 두 값이 다른지 검증 |
assertThat(a).isNull() | 객체가 null인지 검증 |
assertThat(a).isNotNull() | 객체가 null이 아닌지 검증 |
assertThat(a).isSameAs(b) | 두 객체가 동일한지 검증 |
assertThat(a).isNotSameAs(b) | 두 객체가 서로 다른지 검증 |
assertThatThrownBy(() -> {...}) .isInstanceOf(Exception.class) |
특정 예외가 발생하는지 검증 |
assertThatCode(() -> {...}) .doesNotThrowAnyException() |
특정 코드가 예외를 발생시키지 않는지 검증 |
assertThat(a).contains(b) | 문자열 또는 컬렉션이 특정 값을 포함하는지 검증 |
assertThat(a).startsWith(b) | 문자열이 특정 값으로 시작하는지 검증 |
assertThat(a).endsWith(b) | 문자열이 특정 값으로 끝나는지 검증 |
assertThat(a).matches(b) | 문자열이 특정 정규 표현식과 일치하는지 검증 |
assertThat(a).hasSize(b) | 컬렉션의 크기가 특정 값과 같은지 검증 |
assertThat(a).containsExactlyElementsOf(b) | 컬렉션이 특정 요소를 정확히 포함하는지 검증 |
assertThat(a).extracting("필드명") | 컬렉션 내 객체에서 특정 필드를 추출하여 검증 |
assertThat(a).isGreaterThan(b) | 값이 특정 값보다 큰지 검증 |
assertThat(a).isLessThan(b) | 값이 특정 값보다 작은지 검증 |
assertThat(list).doesNotContain(a) | 리스트가 특정 값을 포함하지 않는지 검증 |
이 외에도 다양한 메서드가 제공된다.
🔽 isEqualTo
@Test
@DisplayName("isEqualTo 메서드로 두 값이 같은지 비교한다")
void test() {
final var actual = 1 + 2;
final var expected = 3;
assertThat(actual).isEqualTo(expected);
}
🔽 isNotEqualTo
@Test
@DisplayName("isNotEqualTo 메서드로 두 값이 다른지 비교한다")
void test() {
final var actual = 1 + 2;
final var unexpected = 4;
assertThat(actual).isNotEqualTo(unexpected);
}
🔽 isNull
@Test
@DisplayName("isNull 메서드로 객체가 null인지 비교한다")
void test() {
final Object actual = null;
assertThat(actual).isNull();
}
🔽 isNotNull
@Test
@DisplayName("isNotNull 메서드로 객체가 null이 아닌지 비교한다")
void test() {
final Object actual = new Object();
assertThat(actual).isNotNull();
}
🔽 isSameAs
@Test
@DisplayName("isSameAs 메서드로 두 객체가 같은 객체인지 비교한다")
void test() {
final var actual = new Object();
final var expected = actual;
assertThat(actual).isSameAs(expected);
}
🔽 isNotSameAs
@Test
@DisplayName("isNotSameAs 메서드로 두 객체가 같은 객체인지 비교한다")
void test() {
final var actual = new Object();
final var unexpected = new Object();
assertThat(actual).isNotSameAs(unexpected);
}
🔽 assertThatThrownBy, isInstanceOf, hasMessage
@Test
@DisplayName("assertThatThrownBy 메서드로 특정 예외가 발생하는지 비교한다")
void test() {
assertThatThrownBy(() -> causeException())
.isInstanceOf(IllegalCallerException.class)
.hasMessage("예외가 발생했습니다.");
}
private void causeException() {
throw new IllegalCallerException("예외가 발생했습니다.");
}
🔽 assertThatCode, doesNotThrowAnyException
@Test
@DisplayName("assertThatCode 메서드로 특정 코드가 예외를 발생하지 않는지 비교한다")
void test() {
assertThatCode(() -> {
final var number = Integer.valueOf(0x80000000);
}).doesNotThrowAnyException();
}
🔽 contains
@Test
@DisplayName("contains 메서드로 문자열에 특정 문자열이 포함되어 있는지 비교한다")
void test() {
final var actual = "Hello, world!";
final var expected = "world";
assertThat(actual).contains(expected);
}
@Test
@DisplayName("Collection에 특정 객체가 포함되어 있는지 비교한다")
void test() {
final var actual = List.of(1, 2, 3);
final var expected = 1;
assertThat(actual).contains(expected);
}
🔽 startsWith
@Test
@DisplayName("startsWith 메서드로 문자열이 특정 문자열로 시작하는지 비교한다")
void test() {
final var actual = "Hello, world!";
final var expected = "Hello";
assertThat(actual).startsWith(expected);
}
🔽 endsWith
@Test
@DisplayName("문자열이 특정 문자열로 끝나는지 비교한다")
void test() {
final var actual = "Hello, world!";
final var expected = "world!";
assertThat(actual).endsWith(expected);
}
🔽 matches
@Test
@DisplayName("문자열이 정규 표현식과 일치하는지 비교한다")
void test() {
final var actual = "Hello, world!";
final var expected = "Hello, [a-z]+!";
assertThat(actual).matches(expected);
}
🔽 hasSize
@Test
@DisplayName("Collection의 크기를 비교한다")
void test() {
final var actual = List.of(1, 2, 3);
final var expected = 3;
assertThat(actual).hasSize(expected);
}
🔽 containsExactlyElementsOf, containsExactly
@Test
@DisplayName("Collection에 특정 객체들이 포함되어 있는지 비교한다")
void test() {
final var actual = List.of(1, 2, 3);
final var expected = List.of(1, 2, 3);
assertThat(actual).containsExactlyElementsOf(expected);
assertThat(actual).containsExactly(1, 2, 3);
}
🔽 extracting
@Test
@DisplayName("extracting 메서드로 Collection에 포함된 객체들 중 특정 필드를 추출한다")
void test() {
class User {
private final String username;
private final String password;
User(final String username, final String password) {
this.username = username;
this.password = password;
}
}
final var actual = List.of(
new User("user1", "password1"),
new User("user2", "password2"),
new User("user3", "password3")
);
final var expected = List.of("user1", "user2", "user3");
assertThat(actual).extracting("username").containsExactlyElementsOf(expected);
}
⚠️ extracting 메서드는 Reflection을 사용하므로 주의가 필요하다.
📍 참고 자료
'Programming > Java' 카테고리의 다른 글
[Java] 인터페이스, 추상 클래스, 일반 상속 (0) | 2025.03.22 |
---|---|
[Java] 제네릭(Generic) (0) | 2025.03.17 |
[Java] 컬렉션 프레임워크(Collection Framework) (0) | 2025.03.11 |
[Java] 람다 표현식, 함수형 인터페이스, Stream API (0) | 2025.02.23 |
[Java] Enum (5) | 2024.11.05 |