본문 바로가기
Spring/테스트

[테스트 주도 개발 시작하기] Chap05 JUnit5 기초

by compile-this 2024. 9. 3.

 

 

JUnit 5 모듈 구성

JUnit5 모듈은 아래 세가지로 구성된다.

  • JUnit Platform
    • 테스팅 프레임워크를 구동하기 위한 launcher와 test engine을 위한 API 제공
  • JUnit Jupiter
    • JUnit 5를 위한 테스트 API와 실행 엔진을 제공
  • JUnit Vintage
    • JUnit3과 4로 작성된 테스트를 JUnit5 platform에서 실행하기 위한 모듈 제공

 

JUnit5 모듈 구성

 

gradle의 test 관련 설정에서 JUnit5의 platform과 jupiter에 대해 살펴볼 수 있다. 

 

plugins {
    id 'java'
}

group = 'org.example'
version = '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    testImplementation platform('org.junit:junit-bom:5.9.1')
    testImplementation 'org.junit.jupiter:junit-jupiter'
}

test {
    useJUnitPlatform()
}

 

@Test 애노테이션과 테스트 메서드

@Test 어노테이션을 테스트 메서드에 붙이면 JUnit모듈을 통해 테스트를 실행할 수 있다.

 

(* JUnit은 테스트 메서드를 실행하기 위해 리플렉션(Reflection)을 사용하며, 리플렉션은 private 메서드에
    접근할 수 없기 때문에 @Test어노테이션을 붙인 메서드는 private이면 안된다. )

 

 

주요 단언(Assertions) 메서드

JUnit의 Assertions 클래스는 값을 검증하기 위한 목적의 다양한 정적 메서드를 제공한다.

 

assertEquals()

  • 두 객체가 같은 값을 갖는지 비교
@Test
void sameObjectAssertEquals_Then_TestPassed() {
    LocalDate dateTime1 = LocalDate.now();
    LocalDate dateTime2 = LocalDate.now();

    assertEquals(dateTime1, dateTime2);
}

 

fail()

  • 테스트에 실패했음을 알리고 싶을 때 사용
  • 정상적인 흐름으로 동작하지 않음을 검사할 수 있다.
@Test
void failMethodCalled_Then_TestFailed() {
    boolean throwException = false;

    try {
        this.throwIndeedException(throwException);
        fail(); // 이 지점에 다 다르면 fail() 메서드는 테스트 실패 에러를 발생
    } catch (Exception e) {
        return;
    }
}

 

assertThrows()

  • 지정한 exception이 발생하는지 검사
@Test
void rightExceptionByAssertThrows_Then_TestPassed() {
    assertThrows(IllegalArgumentException.class, () -> {
        this.throwIndeedException(true);
    });
}

 

assertAll()

  • assert 메서드가 실패하면 다음 코드를 실행하지 않고 바로 exception이 발생한다.
@Test
void beforeAssertionsFailed_Then_NextAssertionsNotCalled() {
    assertEquals(1, 2); // 검증 실패로 에러 발생
    assertEquals(1, 1); // 이 코드는 실행되지 않음
}

 

  • 위 경우와 반대로 모든 검증을 실행하고 그중에 실패한 것이 있는지 확인하고 싶을 때 assertAll() 사용
@Test
void assertAllHasFailedCase_Then_AllExecute() {
    assertAll(
            () -> assertEquals(1, 2),
            () -> assertEquals(1, 1),
            () -> assertEquals(3, 4),
            () -> assertEquals(4, 4)
    );
}

 

 

테스트 라이프 사이클

 

아래  코드의 출력결과를 통해 테스트의 life cycle에 대해서 확인해보자.

public class LifeCycleTest {
    public LifeCycleTest() {
        System.out.println("call LifeCycleTest constructor");
    }

    @BeforeAll
    static void init() {
        System.out.println("call @BeforeAll method");
    }

    @BeforeEach
    void setup() {
        System.out.println("call @BeforeEach method");
    }

    @AfterAll
    static void cleanup() {
        System.out.println("call @AfterAll method");
    }

    @AfterEach
    void teardown() {
        System.out.println("call @AfterEach method");
    }

    @Test
    void a() {
        System.out.println("call @Test method a");
    }

    @Test
    void b() {
        System.out.println("call @Test method b");
    }
}

 

 

[출력 결과]

call @BeforeAll method

call LifeCycleTest constructor
call @BeforeEach method
call @Test method a
call @AfterEach method

call LifeCycleTest constructor
call @BeforeEach method
call @Test method b
call @AfterEach method

call @AfterAll method

 

출력 결과를 통해 테스트의 기본적인 life cycle에 대해서 확인해볼 수 있다.

  1. @BeforeAll 어노테이션 메서드 실행
    • @BeforeAll: 테스트 클래스의 모든 테스트 메서드를 실행하기 전에 한 번 실행된다.
  2. 테스트 클래스 인스턴스 생성
    • 테스트 메서드마다 독립적인 환경을 보장하기 위해, 테스트 메서드를 실행하기전에 테스트 클래스를 초기화한다.
  3. @BeforeEach 어노테이션 메서드 실행
    • @BeforeEach: 각 테스트 메서드가 호출되기 전에 실행된다.
  4. @Test 메서드 실행
  5. @AfterEach 어노테이션 메서드 실행
    • @AfterEach: 각 테스트 메서드가 종료된 후에 실행된다.
  6. @AfterAll 어노테이션 메서드 실행 (테스트 클래스의 모든 테스트 메서드가 호출되었다고 가정)
    • @AfterAll: 테스트 클래스의 모든 테스트 메서드가 실행된 후에 실행된다.

 

 

테스트 메서드 간 실행 순서 의존과 필드 공유하지 않기

 

아래 테스트 코드를 살펴보면 FileOperator타입의 변수 op를 각 테스트 메서드에서 공유하고 있다.

메서드의 실행순서를 지정하지 않는다면, readFileTest가 먼저 실행될 수 있고, 제대로된 테스트가 수행되지 않게 된다.

또한 각 테스트 메서드는 독립적으로 실행되어야 하기 때문에 아래 코드처럼 상태(필드)를 테스트 메서드끼리 공유하는건 옳지 않다.

public class BadTest {
    private FileOperator op = new FileOperator();
    private static File file;

    @Test
    void fileCreationTest() {
        File createdFile = op.createFile();
        assertTrue(createdFile.length() > 0);
        this.file = createdFile;
    }

    @Test
    void readFileTest() {
        long data = op.readDate(file);
        assertTrue(data > 0);
    }
}