프로그래밍언어/Java

[Java] JUnit 이란?

멍토 2022. 3. 20.

JUnit

Junit이란 자바 개발자의 93%가 사용하는 단위 테스트 프레임워크이며 Java8 이상부터 지원한다.

JUnit5의 경우 2017년 10월에 공개

스프링부트의 경우 2.2버전부터 기본적으로 제공된다.

 

전처리 작업1(모든 테스트 실행전후 한번만)

// JUnit4
@BeforeClass, @AfterClass
​
// JUnit5
@BeforeAll, @AfterAll

 

스태틱으로 동작하기 때문에 변수에 값을 할당하고 싶다면 변수도 static으로 선언해야 한다.

import org.junit.jupiter.api.BeforeAll;
​
class JUnitTest {
​
    private static String example;
​
    @BeforeAll
    static void setup() {
        example = "여기서 매핑";
    }
}

 

전처리 작업2(모든 테스트 실행전후마다 반복)

// JUnit4
@Before, @After
​
// JUnit5
@BeforeEach, @AfterEach
import org.junit.jupiter.api.BeforeAll;
​
class JUnitTest {
​
    private String example;
​
    @BeforeEach
    void setup() {
        example = "여기서 매핑";
    }
}

 

테스트 제외

// JUnit4
@Ignore
​
// JUnit5
@Disabled

테스트를 작성한 코드를 수정하고 있을경우 제외를 시킴으로써 테스트를 통과시킨다.

import org.junit.jupiter.api.BeforeAll;
​
class JUnitTest {
​
    @Test
    @Disabled("문제가 해결될 때까지 테스트 중단")
    void test() {
      System.out.println("테스트1");
    }
  
    @Test
    void test2() {
      System.out.println("테스트2");
    }
}

 

테스트 이름표기

// JUnit4
@Test
void 테스트_이름은_이렇게_작성한다() {
  
}
​
// Junit5
@DisplayName("굉장한 테스트 입니다.")
@Test
void test() {
  
}

DisplayName을 적용함으로써 이모지나 공백등의 특수문자가 들어갈 수 있게 되었다.

(부작용으로 메서드 명을 고민해야 한다.)

 

테스트 반복

@RepeatedTest(10)
// 해당 메서드를 10번 테스트한다.

랜덤한 요소를 체크하거나 성능상 이슈를 체크할때 주로 사용한다.

 

테스트 매개변수를 변경하여 테스트

@ParameterizedTest를 이용하면 인자를 바꿔가며 테스트를 할 수 있다.

범위에 대한 테스트나 경계값 테스트 등의 여러가지 테스트를 편하게 진행할 수 있다.

매개변수는 아래와 같은 방법으로 넘길 수 있다.

 

@ValueSource

@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
@DisplayName("로또의 숫자 중 중복된 값이 있다면 에러가 발생한다.")
void createWhenDuplicateNumber(int number) {
 
}

단순히 값이 하나만 바뀔때는 ValueSource를 이용하면 쉽게 값을 바꿔가며 테스트가 가능하다.

 

@CsvSource

@ParameterizedTest
@CsvSource(value = {"1:true", "7:false"}, delimiter = ':')
@DisplayName("로또에 숫자의 포함여부를 확인한다.")
void contains(int number, boolean expected) {
  
}

여러개의 값을 넣고 테스트를 하고싶다면 CsvSource를 이용하면 된다.

value는 값들을 넣고, delimiter에 구분자를 넣어서 문자열을 분리해 매개변수에 맞는 타입으로 변환하여 주입해준다.

 

@MethodSource

@ParameterizedTest
@MethodSource("getReward")
@DisplayName("Reward의 당첨 횟수를 반환한다.")
void testGetRewardCountWithReward(Item item) {
  // given
  final Map<Reward, Integer> rewardCounter = getRewardCounter();
  final LottoResult lottoResult = new LottoResult(rewardCounter);
​
  // when
  final int rewardCount = lottoResult.get(item.reward);
​
  // then
  assertThat(rewardCount).isEqualTo(item.count);
}
​
private static Stream<Item> getReward() {
  return Stream.of(
    new Item(Reward.FIRST, 1),
    new Item(Reward.SECOND, 0),
    new Item(Reward.THIRD, 2),
    new Item(Reward.FOURTH, 4)
  );
}
​
private static class Item {
​
  private final Reward reward;
  private final int count;
​
  public Item(Reward reward, int count) {
    this.reward = reward;
    this.count = count;
  }
}

CsvSource만으로 값을 표현하지 못할때가 있다.

이럴때는 MethodSource를 이용하여 처리를 하면된다.

 

테스트 방법

테스트야 사람마다 방법이 다르지만 내가 배운방법은 아래와 같다.

@DisplayName("test 입니다.")
@Test
void test() {
  // given
  // given에서는 값을 초기화 하는 등의 전처리 작업을 한다.
  
  // when
  // when에서는 우리가 목표로 하는 기능을 동작시킨다.
  
  // then
  // then에서는 우리가 원한 결과가 나왔는지 확인한다.
}

위와 같은 구조를 이용하면 테스트 코드를 보았을때 원하는 목적이 무엇인지 알기가 쉬워진다.

 

Assertions

테스트 케이스의 수행 결과를 판별하는 메서드이다.

@DisplayName("확장자 없는 이름이 들어간다면 zookeeper에 데이터가 있을시 확장자 이름으로 바꿔준다.")
@ParameterizedTest
@CsvSource(value = {"ato:ato.mung", "sun:sun.flower"}, delimiter = ':')
void checkUsername(String nickName, String expected) {
  // given
​
  // when
  String value = memberService.checkUsername(nickName);
​
  // then
  Assertions.assertEquals(value, expected);
}

더 많은 내용은 문서로 확인하자.

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

 

AssertJ

테스트를 할때 JUnit에서 제공하는 Assertions말고 AssertJ를 이용할수도 있다.

AssertJ는 테스트를 더 다방면으로 테스트 할 수 있도록 도와주는 오픈소스 라이브러리이다.

testImplementation 'org.assertj:assertj-core:버전'
@ParameterizedTest
@CsvSource(value = {"1:true", "7:false"}, delimiter = ':')
@DisplayName("로또에 숫자의 포함여부를 확인한다.")
void contains(int number, boolean expected) {
  // given
  Lotto lotto = new Lotto(List.of(1, 2, 3, 4, 5, 6));
​
  // when
  boolean isContains = lotto.contains(number);
​
  // then
  assertThat(isContains).isEqualTo(expected);
}

Assertions와 차이는 비교할 값들을 구분해서 처리하고 있다.

// 같은지 비교
assertThat(isContains).isEqualTo(expected);
// 다른지 비교
assertThat(numbers.size()).isNotEqualTo(pivot);
// 내부 크기 비교
assertThat(lotto.getNumbers()).hasSize(EXPECTED_SIZE);
// 에러 클래스 비교
assertThatThrownBy(callable).isExactlyInstanceOf(IllegalArgumentException.class);
// 클래스 비교 + 에러 메시지 비교
assertThatThrownBy(() -> board.addComment(comment))
            .isExactlyInstanceOf(GoodDayException.class)
            .hasMessage(BoardExceptionSet.ALREADY_EXISTED_COMMENT.getMessage());
// true 인지 비교
assertThat(bishop.checkPositionRule(start, validDestination)).isTrue();
// false 인지 비교
assertThat(bishop.checkPositionRule(start, invalidDestination)).isFalse();
// null 비교
assertThat(board.getComment()).isNull();
// null이 아닌지 비교
assertThat(board.getComment()).isNotNull();
// 에러가 발생하지 않았는지 비교
assertThatCode(() -> board.validateUpdateBoard())
            .doesNotThrowAnyException();
// 객체는 다르지만 값이 같은지 비교
assertThat(expected).usingRecursiveComparison().isEqualTo(findBoard);

더 많은 내용은 문서로 확인하자

http://joel-costigliola.github.io/assertj/assertj-core-quick-start.html

 

예외처리방법

// JUnit4
@Test(expected = Exception.class)
​
// JUnit5
@Test
void exceptionTest() {
  assertThrows(Exception.class, () -> "무엇인가 실행");
}
​
// assertJ
assertThatThrownBy(() -> 에러발생 로직).isExactlyInstanceOf(IllegalArgumentException.class);

 

참고

https://www.youtube.com/watch?v=EwI3E9Natcw 

 

댓글

💲 광고입니다.