Programming/Java

[Java] 테스트 코드, JUnit & AssertJ

soeun2537 2025. 2. 21. 18:40
우아한테크코스 레벨 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을 사용하므로 주의가 필요하다.

 

 

📍 참고 자료