[TDD] JUnit5 단위테스팅 총 정리(정의, 어노테이션)

Jae Eon·2021년 5월 19일
0

자바 공부

목록 보기
2/2
post-thumbnail

들어가며

이 포스트는 TDD를 적용하기위해 필요한 단위테스팅의 정의와 JUnit5 어노테이션들을 소개하고 어노테이션 사용방법을 정리하기위해 작성 되었습니다.


🍊 JUnit의 등장 배경

JUnit은 자바 테스트 프레임워크다.
JUnit 등장 이전에 main에서 테스트를 했었고 많은 문제가 있었다.

main의 문제점

  • 구현 코드와 테스트 코드가 클래스 하나에 존재한다. 클래스 크기가 커짐. 복잡도 증가함.
  • 테스트 코드가 실 서비스에 같이 배포됨.
  • 메인 함수 하나에서 여러 개의 기능을 테스트 함. 복잡도 증가.
  • 함수 이름을 통해 어떤 부분을 테스트하는지에 대한 의도를 드러내기 힘듦.
  • 테스트 결과를 사람이 수동으로 확인

이러한 문제점을 보안 하기 위해 등장했다.

🍒 JUnit5 기본 Annotation 소개

테스트 메소드 Annotation

  • @Test : 테스트 메소드를 선언하기 위해 사용한다.

  • @ParameterizedTest :@Test와 달리 @ParameterizedTest 어노테이션은 테스트 메서드가 인자를 받을 수 있도록 한다.

  • @DisplayName : 테스트 클래스나 테스트 메소드에 이름을 붙여줄 때 사용한다

  • @Nested : test 클래스안에 Nested 테스트 클래스를 작성할 때 사용되며, static이 아닌 중첩클래스, 즉 Inner 클래스여야만 한다. 테스트 인스턴스 라이플사이클이 per-class 로 설정되어 있지 않다면 @BeforeAll , @AfterAll 가 동작안하니 주의하자.

  • @Tag : 테스트를 필터링할 때 사용한다. 클래스또는 메소드레벨에 사용한다.

  • @Disabled : 테스트 클래스나, 메소드의 테스트를 비활성화 한다.

  • @Timeout : 주어진 시간안에 테스트가 끝나지 않으면 실패한다.

  • @ExtendWith : extension을 등록한다. 이 어노테이션은 상속이 된다. 확장팩이라고 생각하면 될 것 같다.

  • @RegisterExtension : 필드를 통해 extension을 등록한다. 이런 필드는 private이 아니라면 상속된다.

  • @TempDir : 필드 주입이나 파라미터 주입을 통해 임시적인 디렉토리를 제공할 때 사용한다.


라이프 사이클 메소드 Annotation
라이프 사이클 메소드란 테스트 메소드가 실행되기 전 OR 후 에 실행되는 메소드로 보통 공통적인 세팅을 위해 사용한다.

  • @BeforeEach : 각각 테스트 메소드가 실행되기전에 실행되어야 하는 메소드를 명시해준다.
    @Test , @RepeatedTest , @ParameterizedTest , @TestFactory 가 붙은 테스트 메소드가 실행하기 전에 실행된다.

  • @BeforeAll : @BeforeEach 는 각 테스트 메소드 마다 실행되지만, 이 어노테이션은 테스트가 시작하기 전 딱 한 번만 실행 된다.

  • @AfterEach : @Test , @RepeatedTest , @ParameterizedTest , @TestFactory 가 붙은 테스트 메소드가 실행되고 난 후 실행된다.

  • @AfterAll : 이것도 위와 같다. 테스트가 완전히 끝난 후 딱 한 번만 실행 된다.

🍓 검증을 위한 테스트 코드 작성하기

검증을 위해 HashSet을 생성하고 HashSet에 대해 검증하겠습니다.

numbers 라는 HashSet을 생성하고 정수를 add() 했습니다.
따라서 현재 numberssize()는 3입니다.

@Test
    void setSizeTest() {
    	numbers = new HashSet<>();
        numbers.add(1);
        numbers.add(1);
        numbers.add(3);
        numbers.add(2);
    }

🍋 검증 코드 작성하기 (Assertion)

Assertions의 종류는 아래 2가지가 있으며

  • import org.assertj.core.api.Assertions;
  • import org.junit.jupiter.api.Assertions;

가독성을 위해 위에 있는 core.api.Assertions를 사용하기를 권장합니다.

  • assertThat()
    파라미터를 검증할때 사용합니다.
@Test
    void setSizeTest() {
    	numbers = new HashSet<>();
        numbers.add(1);
        numbers.add(1);
        numbers.add(3);
        numbers.add(2);
        
        //검증 코드 추가 numbers.size()가 3과 동일하면 true를 반환합니다.
        Assertions.assertThat(numbers.size()).isEqualTo(3);
    }

🍑 @ParameterizedTest로 인자 넘기기

@ParameterizedTest 메서드는 인자를 획득하기 위해 @ValueSource 등의 어노테이션을 추가로 요구한다.

아래는 그 예시이다(없는건 아직 사용을 해보지 않아서 추가하지 않았다).

@ValueSource

ValueSource는 원시 자료형의 배열을 메서드에 전달하고 전달된 배열의 크기만큼 테스트를 반복하여 실행합니다.
(파라미터로 배열의 원소가 하나씩 들어온다)

아래👇 코드는 3회 실행되고 number에 1,2,3이 한번씩 검증됩니다.

// @ValueSource는 문자열, 정수 등, 원시 자료형의 배열을 메서드에 전달할 수 있다.
@ParameterizedTest
    @ValueSource(ints = {1, 2, 3})
    void setContainTest(int number) {
    	numbers = new HashSet<>();
        numbers.add(1);
        numbers.add(1);
        numbers.add(3);
        numbers.add(2);
        
        assertThat(numbers.contains(number)).isTrue();
    }

아래는👇 int 말고 String을 사용한 예제입니다.
단순히 argument Null이 아닌지만 검증합니다.

@ParameterizedTest
@ValueSource(strings = { "Hello", "World" })
void testWithStringParameter(String argument) {
    assertNotNull(argument);
}

@CsvSource

@CsvSource는 한번에 여러 값들을 배열로 넘길때 사용합니다.
기본적으로 쉼표가 열 구분자이고
delimiter 속성을 사용하여 사용자가 정의 할 수 있습니다 .

@ParameterizedTest
@CsvSource({"test,TEST", "tEst,TEST", "Java,JAVA"})
void toUpperCaseValue(String input, String expected) {
    String actualValue = input.toUpperCase();
    assertThat(actualValue).isEqualTo(expected);
}
@ParameterizedTest
@CsvSource({"test:TEST", "tEst:TEST", "Java:JAVA"}, delimiter = ':')
void toUpperCaseValue(String input, String expected) {
    String actualValue = input.toUpperCase();
    assertThat(actualValue).isEqualTo(expected);

@MethodSource

@MethodSource는 복잡한 argument를 전달 할 때 사용합니다.
스트림 형태가 대부분이지만 리스트와 같은 다른 컬렉션도 가능합니다.

아래👇 코드에서 스트림형태로 Stringboolean이 argument로 넘어오도록 provideStringsForIsBlank()메서드를 정의하고 메서드를 @MethodSource의 이름으로 입력합니다.

만일 이름이 입력되지 않는다면 테스트 메소드의 이름인 methodSourceTest 라는 메소드를 클래스 내부에서 찾게 됩니다.

    @ParameterizedTest
    @MethodSource("provideStringsForIsBlank")

    void methodSourceTest(String input, boolean expected) {
    assertEquals(expected, Strings.isBlank(input));
    }
private static Stream<Arguments> provideStringsForIsBlank() {
    return Stream.of(
      Arguments.of(null, true),
      Arguments.of("", true),
      Arguments.of("  ", true),
      Arguments.of("not blank", false)
    );
}

@NullSource

@NullSource는 파라미터로 단순히 Null값을 넘겨줍니다.

@ParameterizedTest
@NullSource
void isBlank_ShouldReturnTrueForNullInputs(String input) {
    assertTrue(Strings.isBlank(input));
}

🥭 라이프 사이클 메소드 사용하기

HashSet을 검증하는 위 코드에서 중복되는 코드가 있습니다.
바로 아래와 같은 HashSet을 선언하고 초기화시키는 코드입니다.

@Test
    void setSizeTest() {
    	numbers = new HashSet<>();
        numbers.add(1);
        numbers.add(1);
        numbers.add(3);
        numbers.add(2);
        
        //검증 코드 추가 numbers.size()가 3과 동일하면 true를 반환합니다.
        Assertions.assertThat(numbers.size()).isEqualTo(3);
    }

이런 상황에서 모든 테스트 메서드 실행 전 호출 되는
@BeforeEach 를 적용 하면 코드를 간결하게 만들수 있습니다.

적용시 아래와 같이 코드가 간결해 집니다.

    @BeforeEach
    void setUp() {
        numbers = new HashSet<>();
        numbers.add(1);
        numbers.add(1);
        numbers.add(3);
        numbers.add(2);
    }
    
     @Test
    void setSizeTest() {
        assertThat(numbers.size()).isEqualTo(3);
    }
    
    @ParameterizedTest
    @ValueSource(ints = {1, 2, 3})
    void setContainTest(int number) {      
        assertThat(numbers.contains(number)).isTrue();
    }
profile
🖋정리를 안하면 잊어버린다.👣한 발자국씩 가보자!

0개의 댓글