Java, 테스트 코드 작성 - JUnit 5

작성일

JUnit 5 소개

JUnit 5란?

  • 자바 개발자가 가장 많이 사용하는 테스팅 프레임워크
    • 단위 테스트를 작성하는 자바 개발자 93%가 JUnit을 사용
  • 자바 8 이상을 필요로 함

Junit 5의 세부 모듈 3가지

  1. JUnit Platform: 테스트 코드를 실행해주는 런처를 제공. TestEngine API 제공
  2. Jupiter: TestEngine API 구현체로 JUnit 5를 제공
  3. Vintage: JUnit 4와 3을 지원하는 TestEngine 구현체

JUnit 5: 시작하기

참고
Junit 5부터는 public을 안붙여도 된다. (리플렉션을 사용하기 때문에 public 을 굳이 붙일 필요가 없어짐)

기본 어노테이션

어노테이션을 사용하는 메소드를 구현할 때는 반드시 static을 붙여야 한다. default 는 되고 private 은 안된다. 그리고 리턴타입이 있으면 안됨.

→ 기본적으로 static void 써야한다고 생각하자

@Test

  • 테스트 메서드임을 나타내는 어노테이션

@BeforeAll / @AfterAll

  • beforeAll 어노테이션: 테스트 클래스 안에 있는 모든 테스트가 실행되기 전 딱 한번 호출 됨

  • afterAll 어노테이션: 테스트 클래스 안에 있는 모든 테스트가 실행된 후 딱 한번 호출 됨

@BeforeEach / @AfterEach

  • beforeEach 어노테이션: 각각의 테스트 메서드가 실행되기 전 호출 됨

  • afterEach 어노테이션: 각각의 테스트 메서드가 실행된 후 호출 됨

@Disabled

  • 테스트 어노테이션이 있어도 Disabled 어노테이션이 붙어있다면 테스트코드가 실행되지 않음

테스트 예제

package me.yes.thejavatest;

import org.junit.jupiter.api.*;

import static org.junit.jupiter.api.Assertions.*;

class StudyTest {

    @Test
    void create() {
        Study study = new Study();
        assertNotNull(study);
        System.out.println("create");
    }

    @Test
    void create1() {
        System.out.println("create1");
    }

    @BeforeAll
    static void beforeAll() {
        System.out.println("before all");
    }

    @AfterAll
    static void afterAll() {
        System.out.println("after all");
    }

    @BeforeEach
    void beforeEach() {
        System.out.println("before each");
    }

    @AfterEach
    void afterEach() {
        System.out.println("after each");
    }

    @Test
    @Disabled
    void create2() {
        System.out.println("create2");
    }

}

JUnit 5: 테스트 이름 표기하기

  • @DisplayNameGeneration
  • @DisplayName

테스트 예제

package me.yes.thejavatest;

import org.junit.jupiter.api.*;

import static org.junit.jupiter.api.Assertions.*;

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class StudyTest {

    @Test
    @DisplayName("스터디 만들기")
    void create() {
        Study study = new Study();
        assertNotNull(study);
        System.out.println("create");
    }
}

JUnit 5: Assertion

org.junit.jupiter.api.Assertions.* 를 사용

자주 사용하는 Assert문

assertEquals(expected, actual)

  • 실제 값이 기대한 값과 같은지 확인
  • 기대하는 값을 왼쪽에 실제 나오는 값을 오른쪽에 적어줌 (필수는 아니지만 api가 의도하는 바는 그렇다)

람다식으로 표현하는 이유

  • 테스트가 실패 했을 때만 연산 (람다식을 사용하지 않은 코드는 테스트를 실패하던 성공하던 무조건 실행)
  • 문자열 연산의 비용이 조금 걱정이 될 때는 람다식을 쓰는게 유리 (성능을 신경쓰는 입장에서)
assertEquals(StudyStatus.DRAFT, study.getStatus(), "스터디를 처음 만들면 상태값이 DRAFT여야 한다.");
assertEquals(StudyStatus.DRAFT, study.getStatus(), () -> "스터디를 처음 만들면 상태값이 DRAFT여야 한다.");

assertEquals(StudyStatus.DRAFT, study.getStatus(), 
        "스터디를 처음 만들면 상태값이 " + StudyStatus.DRAFT + "여야 한다.");
assertEquals(StudyStatus.DRAFT, study.getStatus(),
        () -> "스터디를 처음 만들면 상태값이 " + StudyStatus.DRAFT + "여야 한다.");

assertEquals(StudyStatus.DRAFT, study.getStatus(), new Supplier<String>() {
    @Override
    public String get() {
        return "스터디를 처음 만들면 " + StudyStatus.DRAFT + " 상태다.";
    }
});

assertNotNull(actual)

  • 값이 null이 아닌지 확인

assertTrue(boolean)

  • 다음 조건이 참(true)인지 확인

assertAll(executablesl…)

  • 모든 확인 구문 확인
  • 테스트는 순차적으로 실행되기 때문에 위에서 테스트가 깨지면 그 다음 assert문으로 넘어가지 않지만 이를 동시에 알 수 있는 방법이 있다.
  • 각각 assert문을 실행 할 수도 있지만 모든 assert문의 결과를 한번에 알고 싶을 때 assertAll을 사용해서 람다식으로 표현하면 assert문을 한번에 실행 할 수 있다.
assertAll(
        () -> assertNotNull(study),
        () -> assertEquals(StudyStatus.DRAFT, study.getStatus(), () -> "스터디를 처음 만들면 상태값이 DRAFT여야 한다."),
        () -> assertTrue(study.getLimit() > 0, "스터디 최대 참석 가능 인원은 0보다 커야 한다.")
);

assertThrows(expectedType, executable)

  • 예외 발생 확인
IllegalArgumentException exception =
        assertThrows(IllegalArgumentException.class, () -> new Study(-10));
assertEquals("limit은 0보다 커야 한다.", exception.getMessage());

assertTimeout(duration, executable)

  • 특정 시간 안에 실행이 완료되는지 확인
  • 위의 코드는 테스트가 끝날때까지 테스트가 끝나지 않는다. 즉, 실제 테스트에 오래걸리는 코드가 있다면 그 코드가 끝날때까지 끝나지 않는다. 그러나, 이렇게 하는 것은 비효율적이다. 실제 100밀리세컨드가 끝나면 그냥 테스트가 실패하게 만들고 싶다.(이게 효율적이니까)
  • 그럴때 사용하는 것이 assertTimeoutPreemptively() 이다. (Preemptively: 즉각적인)
  • 그러나, assertTimeoutPreemptively()는 주의해서 사용해야한다. ThreadLocal과 관련해 예상하지 못한 예외가 발생할 수 있다.
assertTimeout(Duration.ofMillis(100), () -> {
    new Study(10);
    Thread.sleep(300);
});

assertTimeoutPreemptively(Duration.ofMillis(100), () -> {
    new Study(10);
    Thread.sleep(300);
});

org.assertj.core.api.Assertions.assertThat

Study actual = new Study(10);
assertThat(actual.getLimit()).isGreaterThan(0);

이렇게 테스트 하는 방법도 있다.

JUnit 5: 조건에 따라 테스트 실행하기

특정한 조건을 만족하는 경우에 테스트를 실행하는 방법

org.junit.jupiter.api.Assumptions

  • assumeTrue(조건)
  • assumingThat(조건, 테스트)

@Enabled 와 @Disabled

  • OnOS
  • OnJre
  • IfEnvironmentVariable
  • If

테스트 예제

package me.yes.thejavatest;

import org.junit.jupiter.api.*;
import org.junit.jupiter.api.condition.*;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.jupiter.api.Assumptions.assumingThat;

class StudyTest {

    @Test
    @DisplayName("스터디 만들기")
    @EnabledOnOs({OS.MAC, OS.LINUX})
    @EnabledOnJre({JRE.JAVA_8, JRE.JAVA_9, JRE.JAVA_10, JRE.JAVA_11})
    @EnabledIfEnvironmentVariable(named = "TEST_ENV", matches = "LOCAL")
    void create() {
        String test_env = System.getenv("TEST_ENV");

        assumeTrue("LOCAL".equalsIgnoreCase(test_env));

        assumingThat("LOCAL".equalsIgnoreCase(test_env), () -> {
            Study actual = new Study(10);
            assertThat(actual.getLimit()).isGreaterThan(0);
        });

    }
}