티스토리 뷰

 

JUnit이란 테스트 주도 개발(TDD)에서 단위 테스트를 하기 위한 자바의 테스팅 프레임워크로, 가장 최근 버전은 JUnit5이다. JUnit5는 이전 버전과는 달리 3개의 모듈로 구성되는데 각 모듈의 간단한 기능은 다음과 같다. 

 

JUnit Platform 

JVM에서 테스팅 프레임워크를 실행하는 기반 플랫폼

 

JUnit Jupiter

JUnit5에서의 테스트 작성을 위한 TestEngine으로, JUnit Vintage 엔진에서는 불가능한 parameterized, nested, dynamic tests 등이 가능함

 

JUnit Vintage

JUnit4 또는 이전 버전과의 호환성을 위한 TestEngine

 

 

JUnit은 테스트 구성을 위해 여러 어노테이션을 제공한다. 다음은 가장 많이 사용되는 기본 어노테이션 목록으로 org.junit.jupiter.api 패키지 아래 모두 선언되어있다. (더 자세한 다른 어노테이션들은 다음 글에서 소개할 것이다.)

 

 @Test
- 테스트 메소드임을 표시
- 테스트 실행 시 @Test 어노테이션이 붙은 메소드를 호출

 @DisplayName
- 테스크 클래스나 테스트 메소드에 대해 테스트 실행 시 화면에 표시되는 테스트 이름을 지정

 @BeforeEach
- 현재 클래스 내의 각 @Test 메소드가 실행되기 전 해당 어노테이션이 붙은 메소드가 항상 실행
- 테스트 실행 전 객체 초기화 등을 수행

 @AfterEach
- 현재 클래스 내의 각 @Test 메소드가 실행된 후 해당 어노테이션이 붙은 메소드가 항상 실행
- 테스트 실행 후 자원 할당 해제 등을 수행 

 @BeforeAll
- 현재 클래스 내의 전체 @Test 메소드가 실행되기 전 딱 한번만 해당 어노테이션이 붙은 메소드가 실행 
- 기본 Test Instance Lifecycle을 사용한다면 static 선언 필수

 @AfterAll
- 현재 클래스 내의 전체 @Test 메소드가 모두 실행된 후 딱 한번만 해당 어노테이션이 붙은 메소드가 실행 
- @BeforeAll과 마찬가지로 static 선언 필수

 @TestMethodOrder
- 해당 어노테이션이 붙은 테스트 클래스 내 테스트 메소드들의 실행 순서 관리 정책을 설정 

 @Order
- @TestMethodOrder에서 설정한 테스트 메소드 실행 순서 관리 정책이 OrderAnnotation인 경우, 각 테스트 메소드의 실행 순서를 숫자로 설정 (값이 작을 수록 먼저 실행)

 @Tag
- 클래스 또는 메소드 수준에서 테스트 필터링을 위한 태그를 선언하는데 사용 

 @Disabled
- 해당 테스트 클래스 또는 테스트 메소드가 실행되지 않도록 비활성화 
- JUnit4의 @Ignore과 동일

 @Timeout
- 테스트 메소드의 실행 시간이 지정한 시간을 초과하는 경우 테스트를 fail 시킴

 

@Test 메소드와 @BeforeEach, @AfterAll과 같은 라이프사이클 메소드는 현재 테스트 클래스 내에서 지역적으로 선언되거나 부모 클래스 또는 인터페이스로부터 상속된다. 해당 메소드들은 abstract로 선언 불가하기 때문에 인터페이스에서는 default 메소드로 정의해주어야 한다. 또한 해당 메소드의 반환형은 항상 void로 값을 반환해서는 안된다. (나중에 설명할 @TestFactory 어노테이션이 붙은 메소드는 예외적으로 값을 반환한다.)

 

 

테스트 클래스, 테스트 메소드, 라이프사이클 메소드의 접근제한자는 public일 필요는 없지만 private이어서는 안된다. JUnit에서는 특별한 이유가 아니라면 public 접근제한자를 생략하는 것을 권장한다. (테스트 메소드를 private 접근제한자로 선언한 후 전체 테스트를 실행하면 private 선언된 테스트 메소드는 마치 @Disabled 어노테이션을 붙여준 것처럼 호출되지 않는다. 그러나 해당 테스트 메소드만을 선택하여 실행하면 테스트를 찾을 수 없다는 오류가 발생하므로 테스트 메소드에는 기본적으로 접근제한자를 붙이지 않는다 라고 기억하면 된다.)

 

test 메소드만 실행 시 아래 오류 발생

 

 

 

테스트 클래스 예제

class UnitTest {
	
    @BeforeAll
    static void initAll() {
    	System.out.println("initAll 실행");
    }

    @BeforeEach
    void init() {
    	System.out.println("init 실행");
    }

    @Test
    void firstTest() {
    	System.out.println("firstTest 실행");
    }

    @Test
    void secondTest() {
    	System.out.println("secondTest 실행");
    }

    @AfterEach
    void tearDown() {
        System.out.println("tearDown 실행");
    }

    @AfterAll
    static void tearDownAll() {
    	System.out.println("tearDownAll 실행");
    }
}

 

기본적인 테스트 클래스의 형태는 위와 같다. @BeforeEach, @BeforeAll, @AfterEach, @AfterAll은 각 테스트 또는 모든 테스트의 실행 앞뒤로 호출되는 라이프사이클 메소드이다. 실제 테스트 결과 화면은 다음과 같은데 각각의 firstTest, secondTest 앞뒤로 @BeforeEach, @AfterEach 메소드가 호출되고, 모든 테스트가 실행되기 전, 모든 테스트가 실행된 후로 딱 한번만 @BeforeAll, @AfterAll 메소드가 호출되는 것 또한 확인할 수 있다. 

 

 

 

여기서 기억해야 할 것은 @BeforeAll, @AfterAll 메소드는 static으로 선언되어야 한다는 것이다. JUnit은 테스트 메소드가 독립적으로 수행되도록 기본적으로 각각의 테스트 메소드를 실행하기 전 새로운 테스트 클래스 객체를 생성하고 라이프사이클 메소드를 호출한다. (즉 UnitTest 클래스 안에 테스트 메소드가 5개 존재한다면 UnitTest 클래스 생성자는 총 5번 호출)

 

이러한 JUnit의 라이프사이클 관리 정책으로 모든 테스트 전후로 @BeforeAll, @AfterAll 메소드가 딱 한번만 호출되도록 하기 위해서는 이를 static으로 선언해야 한다. (다음 글에서 더 자세히 설명할 것이므로 이해가 되지 않으면 그냥 @BeforeAll, @AfterAll은 항상 static이라고 생각하면 될 것이다.) 

 

참고로 위 예제는 JUnit 공식문서의 예제를 조금 변경한 것인데, 예제의 @BeforeAll - initAll(), @BeforeEach - init(), @AfterEach - tearDown(), @AfterAll - tearDownAll() 메소드의 정의 순서와 각 메소드 이름은 convention으로 예제와 같이 작성할 것을 권장하고 있다.

 

 

 

위 테스트 결과 화면을 보면 기본적으로 테스트 클래스 이름, 테스트 메소드 이름으로 화면에 표시된다. 이는 @DisplayName 어노테이션을 통해 어떤 테스트인지를 잘 나타내는 이름으로 변경할 수 있다. 

@Test
@DisplayName("첫번째 테스트")
void firstTest() {
    System.out.println("firstTest 실행");
}

@Test
@DisplayName("두번째 테스트")
void secondTest() {
    System.out.println("secondTest 실행");
}

 

@DisplayName 어노테이션의 value 속성에 원하는 이름을 설정해주면 테스트 결과 화면에 다음과 같이 나타난다. 참고로 각 테스트 목록을 클릭하면 테스트별 실행 결과를 확인할 수 있다. 

 

 

 

위 테스트 코드에서의 firstTest, secondTest 메소드 정의 순서대로 테스트가 실행된 것이 아닌가 하는 의문을 가질 수 있다. 그러나 실제로 어떤 알 수 없는 알고리즘으로 테스트 메소드 호출 순서가 거의 랜덤으로 정해진다고 한다. 즉 테스트 메소드들이 어떤 정해진 순서대로 호출되기를 원한다면 이를 @TestMethodOrder 어노테이션으로 강제할 수 있다. 자세한건 다음 글에서 설명하겠지만 사실 실행 순서에 따라 테스트 결과가 달라진다면 이는 나쁜 테스트 코드이다. 앞서 말했듯이 JUnit에서는 각 테스트가 독립적으로 실행되도록 하기 위해 매번 테스트 클래스 객체를 생성한다. 즉 각 테스트는 서로에게 영향을 주지 않도록 작성하는 것이 best practice로 왠만하면 @TestMethodOrder 어노테이션을 사용하는 일이 없도록 하자.

 

 

 

 

마지막으로 @Disabled 어노테이션으로 테스트 클래스나 테스트 메소드의 실행을 비활성화할 수 있다. value 속성에는 비활성화하는 이유를 작성할 수 있는데 JUnit은 이를 작성하도록 권장하고 있다. 만약 이 어노테이션을 테스트 클래스에 선언하면 해당 클래스 내의 모든 테스트 메소드들이 비활성화되며, 테스트 메소드에 선언 시에는 해당 메소드만 비활성화된다.

 

그러나 메소드 레벨에 선언된 @Disabled 어노테이션은 해당 테스트 메소드, 관련 라이프사이클 메소드만 호출되지 않도록 하는 것이지 해당 메소드가 정의된 테스트 클래스의 생성자는 호출된다. (즉 테스트 클래스 안에 2개의 테스트 메소드가 정의되어 있고 그 중 하나가 비활성화되더라도 테스트 클래스 생성자는 2번 호출됨) 사실 이것까지 알아야 하나 싶지만 혹여나 테스트 클래스 생성자에 자원을 소모하는 코드가 존재한다면 이 사실을 아는 것도 중요하지 않을까 싶다.

@Disabled("그냥...")
@Test
@DisplayName("첫번째 테스트")
void firstTest() {
    System.out.println("firstTest 실행");
}

@Test
@DisplayName("두번째 테스트")
void secondTest() {
    System.out.println("secondTest 실행");
}

 

firstTest 비활성화

 

 

간단하게 단위 테스트 코드를 작성할 요량이라면 이정도만 알아도 될 듯 싶고 추가적인 기능이 필요하다면 그때그때 공식문서를 참고하면 좋을 것 같다.

 

 

 

끝.

 

 

 

참고

https://junit.org/junit5/docs/current/user-guide/

 

'자바 > 테스트' 카테고리의 다른 글

[Mockito] Mocking Framework with JUnit5  (1) 2023.10.07
[JUnit] Parameterized Test  (0) 2023.06.24
[JUnit] AssertJ Assertions  (0) 2023.06.23
[JUnit] JUnit5 Assertions  (0) 2023.06.20
[JUnit] 테스트 Tagging과 Filtering  (0) 2023.06.02
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함