상세 컨텐츠

본문 제목

더 자바, "코드를 테스트 하는 다양한 방법" 1부 JUnit 5 (1~2)

본문

1. JUnit 5 시작하기

우선 강의를 보는데 시작부터 실행방법을 몰라 당황했다. 내가 지금까지 프로젝트를 생성한 방법과 달리 Spring Initializr를 이용해 프로젝트를 만들어야 하는데 인텔리제이 목록에 Spring Initializr가 없어서 실행방법을 찾아야 했다. 여러 시행착오 결과 백기선 님의 강의에서 사용하는 옵션은 다음과 같았다.

start.spring.io에서 프로젝트 생성이 가능하다. Project 항목에서 다들 요즘 Gradle을 사용한다 해서 Gradle로 만들어 봤는데 강의에서는 Maven을 사용하길래 Maven을 골랐다. 그리고 밑에 내용은 상황에 맞게 골랐고 Dependencies는 아직 필요 없다 해서 빈칸으로 남기고 다운받았다. 그 후 압축을 풀어 C에 저장한 후 인텔리제이에 드래그해서 프로젝트 생성을 완료하였다. (만약 스프링 부트 프로젝트를 사용하지 않는다면 dependency에 밑의 내용을 추가하면 된다. 

 

<dependency>

    <groupId>org.junit.jupiter</groupId>

    <artifactId>junit-jupiter-engine</artifactId>

    <version>5.5.2</version>

    <scope>test</scope>

</dependency>

 

프로젝트에서 inflearn > src > main > java > Josh.inflearn 에 Study 클래스를 만들고 'ctrl + shift + t' 로 test 클래스를 만들면 새로운 test 클래스가 생긴다. 작업은 이곳에서 이루어진다.

기본 애노테이션들을 우선 배워보자.

  • @Test
  • @BeforeAll / @AfterAll
  • @BeforeEach / @AfterEach
  • @Disabled

다음과 같은 기본 애노테이션들이 있고 실행 코드와 결과는 다음과 같다.

위 2개의 사진은 @Test와 @BeforeAll, @AfterAll, @BeforeEach, @AfterEach를 실행한 결과이다.

각 애노테이션의 특성은 사진에서 보고 전체적인 틀만 보자면 다음과 같다.

<실행>

@BeforeAll

 

@BeforeEach

@Test1

@AfterEach

 

@BeforeEach

@Test2

@AfterEach

 

@AfterAll

<종료>

 

기본 애노테이션에서 아직 설명을 안 한 게 있는데 바로 @Disabled이다. @Disabled를 알기 위해 아래 코드를 보자.

이전 코드와 같은 상황에서 두 번째 @Test 밑에 @Disabled를 적었더니 실행결과 creat2 메소드가 실행되지 않았다.

위와 같이 @Disabled는 @Test를 실행하지 못하게 하는 애노테이션이다.  

 

 

2. JUnit 5 테스트 이름 표시하기

다음은 create와 create2 메소드를 create_new_study 와 create_new_study_again으로 이름을 바꿔 실행한 것이다.

왼쪽 아래 보면 실행한 Test들의 이름이 적혀있는데 보통 Test의 이름은 메소드의 이름으로 나오는 것을 알 수 있다.

( 언더바 _ 를 이용해 이름을 짓는 이유는 띄어쓰기 없이 보면 가독성이 떨어지기 때문이다.)

하지만 언더바 _ 가 거슬려 보이기도 하므로 이를 없애기 위해 @DisplayNameGeneration을 적용할 수 있다.

메소드와 클래스 어디든 적용할 수 있지만 이번엔 클래스에 적용해 보았다. 괄호 안에 DisplayNameGenerator.ReplaceUnderscores.class는 보시다시피 Test이름의 언더바 _ 를 공백으로 표시해 주는 것이고

실제로 왼쪽 하단의 Test이름을 보면 언더바가 다 사라진 모습을 볼 수 있다.

 

두 번째로 @DisplayName을 메소드에 적용한 경우를 보자.

이번엔 메소드에 적용하고 괄호 안에 직접 Test의 이름을 적어 넣었다. @DisplayName은 한글, 이모지 등 여러 표현이 가능하여 Test를 한눈에 알아보기 쉽게 해 준다. 따라서 @DisplayNameGeneration 보다 @DisplayName을 더 많이 사용한다. 그리고 우선순위도 @DisplayNameGeneration 보다 @DisplayName가 더 높다.

 

이번 강의에서는 꿀 tip을 하나 알 수 있었는데 

다음과 같이 19번째 줄(Test 내부)에서 우클릭 후 빨간 괄호 안에 Run을 실행하면 해당 Test만 실행이 된다.

(Ctrl+Shift+F10 도 가능)

그리고 15번째 줄처럼 중립적인 부분을 우클릭하고 Run을 누르면 전체 클래스가 실행된다.

 

3. JUnit 5 : Assertion

 

이제부터는 굳이 코드를 다 올리지 않고 핵심 내용 정도만 올려보려고 한다.

이번 시간에 배운 것은 Assertion이다. 

다음과 같이 assertNotNull(A)는 A가 Null인지 확인하는 Assertion이고

assertEquals(A, B, C)는 B가 A와 같은지 확인하고 다르면 C를 오류 메세지로 보내는 Assertion이다.

assertEquals()에서 A는 기대 값이고 B는 실제 값으로 B값을 A값과 비교한다고 생각하면 된다.

 

위의 사진은 assertNotNull()과 assertEquals()가 True일 때, 즉 오류가 없을 때의 결과 값으로 막힘없이 실행이 완료된다.

그러면 오류가 난 경우는 어떨까?

다음은 assertEquals()에서 오류가 난 경우이다. 기대 값이 DRAFT인데 실제 값이 Null이라 오류가 난 것을 밑의 오류 내용에서 알 수 있고 C값인 오류메세지가 출력된 것도 알 수 있다. 

여기서 오류메세지인 C 값을 람다식(?)으로 바꿀 수 있는데 이러면 어떤 장점이 있을까?

[ 람다식 표현 : () -> "오류메세지"]

람다식을 아직 제대로 공부하지 못해서 잘 모르지만 강의에서 들은 것을 적어보자면, 오류메세지가 문자열의 연산과 같이 복잡할 때 그냥 " 오류메세지(연산)+...+ " 로 적으면 테스트를 실행할 때마다(Test가 성공하든 실패하든) 문자열을 연산해야 해서 비효율적이지만 람다식으로 적으면 Test가 실패할 때만 연산을 해 효율적이다.

 

다음으로 여러 개의 Assertion들이 오류가 나면 어떻게 될지 보자.

다음은 assertEquals()와 assertTrue()가 오류가 있는 경우이다.(assertTrue는 쉬우니 굳이 설명 x)

13번째와 15번째 줄이 오류이지만 13번째 줄인 assertEquals()만 오류메세지가 뜨는 것을 볼 수 있다.

13번째 오류를 바로 잡은 후에야 다음과 같이 15번째 줄의 오류메세지가 출력되는 것을 볼 수 있다.

그러면 하나하나 바로잡으면서 오류를 확인해야 하나? 당연히 한 번에 오류를 볼 수 있는 방법이 따로 있다.

 

모든 Assertion의 오류를 한번에 볼 수 있는 방법을 보겠다.

다음과 같이 assertAll()을 사용하면 괄호 안의 Assertion이 한번에 실행되 모든 오류를 확인할 수 있다.

assertAll()은 괄호 안에 Executable타입이 들어가고 표현 예시를 보면 다음과 같다.

assertAll(

           () -> assert1번 ,

           () -> assert2번 ,

           () -> assert3번 ,

           () -> assert4번 

);

.

다음 알아볼 Assertion은 assertThrows( expectedType, executable)이다.

executable을 실행했을 때 expectedType의 예외가 발생했는지 확인하는 Assertion이고 해당 예외가 발생한다면 오류 없이 실행된다.

다음 코드를 보면 exception(IllegalArgumentException)이라는 변수에 assertThrows(A, B)에서 나오는 오류 값을 저장하고 assertEquals(a,b)로 a의 문자열과 b (exception의 오류메세지 = assertThrows()에서 나온 오류메세지)를 비교하고있다. Study.java에서, assertThrows(A,B)에서 B의 오류를 A(=IllegalArgumentException)와 일치시켰지만 B의 오류 메세지를 a와 살짝 다르게 하여( . 을 빼고 적음) 오류를 발생시켰고 그 결과는 위의 결과 값과 같다.

사실 이 부분은 아직 예외에 익숙하지 않아서 어렵게 느껴지지만 머릿속에 틀만이라도 잡고 넘어가는 것이 좋아 보인다.

 

마지막 Assertion은 assertTimeout(duration, executable)이다. duration은 시간 제한을 의미하고 그 시간 안에 executable이 실행되면 오류 없이 실행되는 Assertion이다. 예시를 보자.

다음과 같이 duration에 100ms를 제한시간으로 입력하고 오른쪽의 람다식을 실행하니 오류가 발생했다.

람다식 내용이 100ms를 넘을 수 밖에 없는 내용이므로 당연한 결과인데 위 사진의 결과들을 보면 총 342ms가 걸렸고 오류메세지에는 이중 람다식 실행이 100ms에서 203ms가 넘는 303ms가 걸렸다고 말하는 것을 알 수 있다.

이렇게 assertTimeout()을 실행하면 제한시간 100ms가 지나도 람다식이 다 실행될 때 까지 기다려야 해서 342ms라는 제한시간의 3배 이상의 시간을 기다리게 된다. 그래서 제한시간이 지나면 람다식을 전부 실행하지 않고 스킵하는 방법이 있다.

다음과 같이 assertTimeoutPreemptively()를 쓰면 제한시간이 끝날 때까지 람다식 실행이 완료되지 않는 경우 바로 람다식 실행을 끝내고 오류메세지를 출력한다. 백기선 님이 ThreadLocal과 같이 공유가 안되는 것들은 assertTimeoutPreemptively()가 적용이 안되므로 assertTimeout()을 써야 한다고 말했는데 아직 이해가 가지 않는 내용이라 넘겨야겠다.

 

위의 Assertion 이외의 여러 라이브러리가 존재하지만 일단은 이정도의 기본 내용만 알고 가자.(AssertJ, Hemcrest, Truth 등)