JUnit 5, AssertJ의 사용법을 간단하게 정리하였습니다. 😁

🤔 JUnit 이란?

  • Java 진영의 대표적인 Test Framework
  • 단위 테스트(Unit Test)를 위한 도구를 제공
    • 단위 테스트란?
      • 코드의 특정 모듈이 의도된 대로 동작하는지 테스트하는 절차를 의미
      • 모든 함수와 메소드에 대해 각각의 테스트 케이스를 작성하는 것
  • 어노테이션(Annotation)을 기반으로 테스트를 지원
  • 단정문(Assert)으로 테스트 케이스의 기대값에 대해 수행 결과를 확인 할 수 있다
  • Spring Boot 2.2 버전 부터 JUnit 5 버전을 사용
  • JUnit은 크게 Jupiter, Platform, Vintage 모듈로 구성




📖 JUnit 5 모듈 설명

JUnit Jupiter

  • TestEngine API 구현체로 JUnit 5를 구현하고 있다.
  • 테스트의 실제 구현체는 별도 모듈 역할을 수행하는데, 그 모듈 중 하나가 Jupiter-Engine이다.
  • 이 모듈은 Jupiter-API를 사용하여 작성한 테스트 코드를 발견하고 실행하는 역할을 수행한다.
  • 개발자가 테스트 코드를 작성할 때 사용된다.

JUnit Platform

  • Test를 실행하기 위한 뼈대
  • Test를 발견하고 테스트 계획을 생성하는 TestEngine 인터페이스를 가지고 있다.
  • TestEngine을 통해 Test를 발견하고, 수행 및 결과를 보고한다. 그리고 각종 IDE 연동을 보조하는 역할을 수행한다.(콘솔 출력 등)
  • Platform = TestEngine API + Console Launcher + JUnit 4 Based Runner 등

JUnit Vintage

  • TestEngine API 구현체로 JUnit 3, 4를 구현하고 있다.
  • 기존 JUnit 3, 4버전으로 작성된 테스트 코드를 실행할 때 사용된다.
  • Vintage-Engine 모듈을 포함하고 있다.

jUnit Module



📖 JUnit 5 어노테이션

어노테이션 가이드 링크

라이프사이클 어노테이션

JUnit 5는 아래와 같은 테스트 라이프 사이클을 가지고 있습니다.

Annotation Description
@Test 테스트용 메소드를 표현하는 어노테이션
@BeforeEach 각 테스트 메소드가 시작되기 전에 실행되어야 하는 메소드를 표현
@AfterEach 각 테스트 메소드가 시작된 후 실행되어야 하는 메소드를 표현
@BeforeAll 테스트 시적 전에 실행되어야 하는 메소드를 표현 (static 처리 필요)
@AfterAll 테스트 종료 후에 실행되어야 하는 메소드를 표현 (static 처리 필요)


@ParameterizedTest

파라미터 테스트를 할 수 있게 도와주는 어노테이션으로, @ValueSource, @CsvSource,@MethodSource 등을 통해 매개변수를 지정할 수 있다.

@ParameterizedTest
@ValueSource(ints = { 1, 5 })
void isPositive(int number) {
  assertTrue(number > 0);
}
@ParameterizedTest
@CsvSource({
    "apple,         1",
    "banana,        2",
    "'lemon, lime', 0xF1",
    "strawberry,    700_000"
})
void testWithCsvSource(String fruit, int rank) {
    assertNotNull(fruit);
    assertNotEquals(0, rank);
}
@ParameterizedTest
@MethodSource("stringProvider")
void testWithExplicitLocalMethodSource(String argument) {
    assertNotNull(argument);
}

static Stream<String> stringProvider() {
    return Stream.of("apple", "banana");
}


@RepeatedTest

반복 테스트를 위해 사용되는 어노테이션

@RepeatedTest(5)
void repeatedTest() {
  System.out.println("테스트 반복!");
}


@TestMethodOrder

우선 순위에 따라 테스트를 실행할 수 있도록 도와주는 어노테이션. @Order 를 통해 우선순위를 지정할 수 있다.

@TestMethodOrder(OrderAnnotation.class)
class OrderedTestsDemo {
  @Test
  @Order(2)
  void secondTest() {
    System.out.println("2");
  }

  @Test
  @Order(1)
  void firstTest() {
    System.out.println("1");
  }
}


@TestInstance

생명주기를 지정할 수 있는 어노테이션

@TestInstance(Lifecycle.PER_CLASS)
class OrderedTestsDemo {...}


@DisplayName

테스트 이름을 지정할 수 있는 어노테이션

@DisplayName("기본 산수를 검증하는 테스트")
@Test
void basic() {
  assertEquals(2, 1 + 1);
}


@Nested

중첩 클래스 테스트라는 것을 명시하는 어노테이션 @BeforeAll 또는 @AfterAll 를 사용하려면 생명 주기를 명시해야한다.

public class NestedTests {

  @TestInstance(Lifecycle.PER_CLASS)
  @Nested
  class Positive {

    @BeforeAll
    void startTest() {
      System.out.println("테스트 시작");
    }

    @Test
    void isPositive() {
      assertTrue(1 > 0);
    }
  }

  @Nested
  class Negative {
    @Test
    void isNegative() {
      assertTrue(-1 < 0);
    }
  }
}


@Tag

태그를 선언하는 어노테이션. 추후에 구성 설정 파일을 통해 테스트에 포함 시킬 태그를 선택하거나, 제외시킬 태그를 선택할 수 있다. JUnit4의 @Category가 이 어노테이션으로 대체됨

@Tag("number")
@Test
void basic() {
  assertEquals(2, 1 + 1);
}
junitPlatform {
  filters {
      tags {
          include 'number', 'korean'
          exclude 'english'
      }
  }
}


@Disabled

테스트에서 제외시키는 어노테이션. JUnit4의 @Ingore와 같은 기능이다.

@Disabled
@Test
void ignoredTest() {
  System.out.println("테스트 하지 않습니다!");
}


@Timeout

실행 시간 제한을 걸어두는 어노테이션. 시간이 초과되면 TimeoutException이 발생하며 테스트가 실패

@Timeout(1)
@Test
void mustRunIn1SecondTest() throws InterruptedException {
	Thread.sleep(1500); // 실패
}

@Test
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
void failsIfExecutionTimeExceeds500Milliseconds() {
    // fails if execution time exceeds 500 milliseconds
}





📖 JUnit Assertions

Assertions 가이드 링크

assertTrue && assertFalse

참 거짓 여부 판단. 2번째 인자가 존재하면 실패 시 2번째 인자를 메시지로 출력해준다.

assertTrue('a' < 'b');
assertTrue('a' < 'b', () -> "message");

assertFalse('a' < 'b');
assertFalse('a' < 'b', () -> "message");


assertNull && assertNotNull

객체의 null 여부 판단

assertNull(System.getProperty("my.prop"));
assertNotNull(System.getProperty("my.prop"));


assertEquals && assertNotEquals

두 값을 비교하여 일치 여부 판단. 3번째 인자가 존재하면 실패 시 3번째 인자를 메시지로 출력해준다.

assertEquals(2, calculator.add(1, 1));
assertEquals(4, calculator.multiply(2, 2), "message");


assertArrayEquals

두 배열을 비교하여 일치 여부 판단. 두 배열이 모두 null이어도 동일한 것으로 간주한다.

char[] expected = {'J','u','n','i','t'};
char[] actual = "Junit".toCharArray();
assertArrayEquals(expected, actual);


assertAll

여러 개의 assertions가 만족할 경우에만 테스를 통과하였다고 판단하고 싶을 경우에는 assertAll()을 사용한다.

@Test
public void test() {
    assertAll(
      "heading",
      () -> assertEquals(4, 2 * 2, "4 is 2 times 2"),
      () -> assertEquals("java", "JAVA".toLowerCase()),
      () -> assertEquals(null, null, "null is equal to null")
    );
}

@Test
void dependentAssertions() {
    assertAll("properties",
        () -> {
            String firstName = person.getFirstName();
            assertNotNull(firstName);
            assertAll("first name",
                () -> assertTrue(firstName.startsWith("J")),
                () -> assertTrue(firstName.endsWith("e"))
            );
        },
        () -> {
            String lastName = person.getLastName();
            assertNotNull(lastName);
            assertAll("last name",
                () -> assertTrue(lastName.startsWith("D")),
                () -> assertTrue(lastName.endsWith("e"))
            );
        }
    );
}


assertSame && assertNotSame

예상되는 값과 실제 값이 동일한 객체를 참조하는지 확인

String language = "Java";
Optional<String> optional = Optional.of(language);

assertSame(language, optional.get());
assertNotSame(language, optional.get());


assertThrows

특정 예외가 발생하였는지 확인. 첫 번째 인자는 확인할 예외 클래스, 두 번째 인자는 테스트하려는 코드

@Test
void exceptionTesting() {
    Exception exception = assertThrows(ArithmeticException.class, () ->
        calculator.divide(1, 0));
    assertEquals("/ by zero", exception.getMessage());
}


assertTimeout & assertTimeoutPreemptively

특정 시간 안에 실행이 끝나는지 확인

  • assertTimeout: 시간 내 실행이 끝나는지 여부 확인 시
  • assertTimeoutPreemptively: 지정한 시간 내 끝나지 않으면 바로 종료
@Test
void timeoutNotExceeded() {
    // 시간안에 성공
    assertTimeout(ofMinutes(2), () -> {
        // 2분 안에 끝나는 테스트 작성...
    });
}

@Test
void timeoutNotExceededWithResult() {
    // 시간 안에 성공, 그리고 supplied object 리턴
    String actualResult = assertTimeout(ofMinutes(2), () -> {
        return "a result";
    });
    assertEquals("a result", actualResult);
}

@Test
void timeoutExceeded() {
    // 시간안에 테스트가 성공하지 못한다면 다음과 비슷한 오류 메시지 출력
    // execution exceeded timeout of 10 ms by 91 ms
    assertTimeout(ofMillis(10), () -> {
        Thread.sleep(100);
    });
}

@Test
void timeoutExceededWithPreemptiveTermination() {
    // 시간안에 테스트가 성공하지 못한다면 다음과 비슷한 오류 메시지 출력
    // execution timed out after 10 ms
    assertTimeoutPreemptively(ofMillis(10), () -> {
        // 10ms 이상 소요되는 작업...
        new CountDownLatch(1).await();
    });
}





📖 AssertJ Assertions

참고

AssertJ에서 모든 테스트 코드는 assertThat()으로 시작한다.

assertThat(타겟).메소드1().메소드2();


Object Assertions

Objects는 두 객체의 동등성이나 객체의 필드를 검사하기 위해 다양한 방법으로 비교할 수 있습니다.

public class Dog { 
    private String name; 
    private Float weight;
    
    // Getters, Setters...
}

Dog fido = new Dog("Fido", 5.25);
Dog fidosClone = new Dog("Fido", 5.25)

isEqualTo()

참조 비교

assertThat(fido).isEqualTo(fidosClone);

isEqualToComparingFieldByFieldRecursively()

내용 비교

assertThat(fido).isEqualToComparingFieldByFieldRecursively(fidosClone);


Boolean Assertions

참, 거짓 확인

isTrue() && isFalse()

assertThat("".isEmpty()).isTrue();


Iterable/Array Assertions

Iterable/Array 가이드 링크
Iterable/Array에 특정 요소가 존재하는지 다양한 방법으로 알 수 있다.

contains()

요소 포함 확인

List<String> list = Arrays.asList("1", "2", "3");
assertThat(list).contains("1");

doesNotContain()

요소가 포함되지 않았는지 확인

assertThat(list).doesNotContain("4");

containsExactly()

실제 그룹에 지정된 값이 순서대로 정확히 포함되어 있는지 확인한다. 일관된 반복 순서가 있는 그룹에만 사용해야 한다.

 Iterable<Ring> elvesRings = newArrayList(vilya, nenya, narya);

 // 성공
 assertThat(elvesRings).containsExactly(vilya, nenya, narya);

 // 실패
 assertThat(elvesRings).containsExactly(nenya, vilya, narya);

containsAll()

실제 그룹에 지정된 Iterable의 모든 요소가 임의의 순서로 포함되어 있는지 확인한다.

Iterable<String> abc = Arrays.asList("a", "b", "c");

 // 성공
 assertThat(abc).containsAll(Arrays.asList("b", "c"))
                .containsAll(Arrays.asList("a", "b", "c"));

 // 실패
 assertThat(abc).containsAll(Arrays.asList("d"));
 assertThat(abc).containsAll(Arrays.asList("a", "b", "c", "d"));

isNotEmpty()

비어있지 않았는지 확인

assertThat(list).isNotEmpty();

startsWith()

주어진 값으로 시작하는지 확인

assertThat(list).startsWith("1");


Character Assertions

Character 가이드 링크
Character에 대한 비교

assertThat(someCharacter)
  .isNotEqualTo('a')             // a 가 아니고
  .inUnicode()                   // 유니코드 테이블에 있고
  .isGreaterThanOrEqualTo('b')   // b보다 크고
  .isLowerCase();                // 소문자 인지 확인


Class Assertions

Class 가이드 링크
클래스에 관한 비교

isInterface()

인터페이스인지 확인

assertThat(Runnable.class).isInterface();

isAssignableFrom()

할당 가능한지 확인

assertThat(Exception.class).isAssignableFrom(NoSuchElementException.class);


File Assertions

File 가이드 링크
파일에 관련된 비교

exists()

존재 여부

assertThat(someFile).exists();

isFile()

파일유형인지 확인

assertThat(someFile).isFile();

canRead()

읽을 수 있는지 확인

assertThat(someFile).canRead();

canWrite()

쓸 수 있는지 확인

assertThat(someFile).canWrite();


Double/Float/Integer Assertions

Double/Float/Integer 가이드 링크
숫자에 대한 비교

isBetween()

값이 두 값 사이에 존재하는지 확인 (start, end 포함)

assertThat(3).isBetween(1,4);

isStrictlyBetween()

값이 두 값 사이에 존재하는지 확인 (start, end 미포함)

assertThat(3).isStrictlyBetween(1,4);

isPositive()

양수인지 확인

assertThat(3).isPositive();

isNegative()

음수인지 확인

assertThat(-1).isNegative();

isZero()

값이 0인지 확인

assertThat(0).isZero();


Map Assertions

Map 가이드 링크
맵에 특정 항목, entry, key/value 값이 포함되어 있는지 확인한다.

containsKey()

키가 존재하지는 확인

assertThat(map).containsKey(2);

doesNotContainKeys()

키가 존재하지 않는지 확인

assertThat(map).doesNotContainKeys(10);

entry()

map의 키, 값을 확인

assertThat(map).contains(entry(2, "a"));


Throwable Assertions

Throwable 가이드 링크
예외 메시지, 스택 추적 검사, 예외 발생했는지 확인

Throwable thrown = catchThrowable(() -> {
    throw new Exception("boom!");
});

assertThat(thrown).isInstanceOf(Exception.class).hasMessageContaining("boom");

assertThatThrownBy()

assertThatThrownBy(() -> {throw new Exception("boom!");})
.isInstanceOf(Exception.class)
.hasMessageContaining("boom");

assertThatExceptionOfType()

특정 타입으로 검사

assertThatExceptionOfType(IOException.class)
.isThrownBy(() -> { throw new IOException("boom!"); })
.withMessage("%s!", "boom")
.withMessageContaining("boom")
.withNoCause();

그 밖에 특정 타입을 검사하는 exceptions를 이용할 수 있다.

  • assertThatNullPointerException
  • assertThatIllegalArgumentException
  • assertThatIllegalStateException
  • assertThatIOException

Describing Assertions

as()

테스트의 설명 작성

assertThat(person.getAge())
    .as("%s's age should be equal to 100", person.getName())
    .isEqualTo(100);

// 출력
// [Alex's age should be equal to 100] expected:<100> but was:<34>


JUnit Assumptions

특정 환경일 때만 테스트를 진행하도록 하고 싶을 때 사용

assumeTrue() && assumeFalse()

테스트가 성공, 실패일 때 계속 진행

@Test
void trueAssumption() {
    assumeTrue("PRD".equals(System.getenv("ENV")));
    // 계속 진행...
}
@Test
void falseAssumption() {
    assumeFalse(5 < 1);
    // 계속 진행...
}

assumingThat()

첫번째 인자 성공시, 두번째 인자 실행

@Test
void assumptionThat() {
    assumingThat("CI".equals(System.getenv("ENV")),
        () -> {
            // CI서버에서만 이 테스트를 실행한다.
            assertEquals(2, calculator.divide(4, 2));
        });
    assertEquals(5 + 2, 7);
}





📖 JUnit import static

기본 import static 해놓으면 좋은 것들을 정리

import static org.hamcrest.Matchers;
import static org.junit.jupiter.api.Assertions.*;
import static org.assertj.core.api.Assertions.*;



댓글남기기