Unit Test 작성 Tip

1. Unit Test 작성 방식

**given/when/then 패턴**

  • 1개의 단위 테스트를 3가지 단계로 나누어 처리하는 패턴
    • given(준비): 어떠한 데이터가 준비되었을 때
    • when(실행): 어떠한 함수를 실행하면
    • then(검증): 어떠한 결과가 나와야 한다.
		
@DisplayName("로또 번호 범위 테스트")
@Test
void lottoNumberRangeTest() {
    // given
    final LottoNumberGenerator lottoNumberGenerator = new LottoNumberGenerator();
    final int price = 1000;

    // when
    final List<Integer> lotto = lottoNumberGenerator.generate(price);

    // then
    assertThat(lotto.stream().allMatch(v -> v >= 1 && v <= 45)).isTrue();
}

2. 이름 지정

  • 테스트 메소드의 이름은 다음 세 부분이 포함되어야 한다.
    • 테스트할 메소드의 이름
    • 테스트 중인 시나리오
    • 테스트 후 예상되는 결과 및 동작
    • 메서드명테스트상태기대행위 형태로 작성한다.

@Test
void isAdult_AgeLessThan18_False () {

}

@Test
void withdrawMoney_InvalidAccount_ExceptionThrown () {

}

@Test
void admitStudent_MissingMandatoryFields_FailToAdmit () {

}

3. 매직넘버 사용 방지

  • 테스트 코드는 테스트 코드 자체가 메뉴얼로도 동작하기 때문에 모호하거나 불명확한 표현은 피하는것이 좋다.
@DisplayName("로또 번호 범위 테스트")
@Test
void lottoNumberRangeTest() {
    // given
    final LottoNumberGenerator lottoNumberGenerator = new LottoNumberGenerator();
    final int PRICE = 1000;
		final int FIRST_LOTTO_NUMBER = 1; // 표현 수정
		final int LAST_LOTTO_NUMBER = 45; // 표현 수정

    // when
    final List<Integer> lotto = lottoNumberGenerator.generate(PRICE);

    // then
    assertThat(lotto.stream().allMatch(v -> v >= FIRST_LOTTO_NUMBER && v <= LAST_LOTTO_NUMBER )).isTrue();
}

4. 로직이 들어가는 것을 최소화

  • 조건문 혹은 반복문 등이 사용하는 것을 최소화 한다.
  • 분기 등의 처리가 불가피한 경우, 테스트를 2개로 분할하는 것이 좋음
  • 반복문에 대해서는 추가 설명 필요!
@DisplayName("로또 당첨 확률 테스트")
@Test
void lottoNumberZeroPercentTest() {
    // given
    final LottoNumberGenerator lottoNumberGenerator = new LottoNumberGenerator();
    final int PRICE = 1000;
		final int FIRST_LOTTO_NUMBER = 1; // 표현 수정
		final int LAST_LOTTO_NUMBER = 45; // 표현 수정
		final Array MYNUMBER = [1,3,4,5,6];
		int rate = 0;
    // when
    final List<Integer> lotto = lottoNumberGenerator.generate(PRICE);

    // then
		MYNUMBER.forEach((number) => {
			if (lotto.contains(number)) {
				rate *= 20
			}
		})
    assertEquals(rate , 0);
}
@DisplayName("로또 당첨 확률 테스트")
@Test
void lottoNumberZeroPercentTest() {
    // given
    final LottoNumberGenerator lottoNumberGenerator = new LottoNumberGenerator();
    final int PRICE = 1000;
		final int FIRST_LOTTO_NUMBER = 1; // 표현 수정
		final int LAST_LOTTO_NUMBER = 45; // 표현 수정
		final Array MYNUMBER = [1,3,4,5,6];

    // when
    final List<Integer> lotto = lottoNumberGenerator.generate(PRICE);

    // then
    assertEquals(lotto.myZeroPercentage(MYNUMBER), 0);
}

5. 다중 동작 방지

  • 각 동작에 대한 별도 테스트를 생성할 것, 테스트가 실패할 경우, 파악하기가 편하다.
@DisplayName("로또 번호 범위 테스트")
@Test
void lottoNumberRangeTest() {
    // given
    final LottoNumberGenerator lottoNumberGenerator = new LottoNumberGenerator();
    final int PRICE = 1000;
		final int FIRST_LOTTO_NUMBER = 1;
		final int LAST_LOTTO_NUMBER = 45;
		final int UNDERFLOWED_FIRST_LOTTO_NUMBER = -1;
		final int OVERFLOWED_LAST_LOTTO_NUMBER = 46;

    // when
    final List<Integer> lotto = lottoNumberGenerator.generate(PRICE);

    // then
    assertThat(lotto.stream().allMatch(v -> v >= FIRST_LOTTO_NUMBER && v <= LAST_LOTTO_NUMBER )).isTrue();
		assertThat(lotto.stream().allMatch(v -> v >= FIRST_LOTTO_NUMBER && v <= OVERFLOWED_LAST_LOTTO_NUMBER )).isFalse();
		assertThat(lotto.stream().allMatch(v -> v >= UNDERFLOWED_FIRST_LOTTO_NUMBER && v <= OVERFLOWED_LAST_LOTTO_NUMBER )).isFalse();
		assertThat(lotto.stream().allMatch(v -> v >= UNDERFLOWED_FIRST_LOTTO_NUMBER && v <= OVERFLOWED_LAST_LOTTO_NUMBER )).isFalse();
}
@DisplayName("로또 번호 범위 통과 테스트")
@Test
void lottoNumberRangeSuccessTest() {
    // given
    final LottoNumberGenerator lottoNumberGenerator = new LottoNumberGenerator();
    final int PRICE = 1000;
		final int FIRST_LOTTO_NUMBER = 1;
		final int LAST_LOTTO_NUMBER = 45;

    // when
    final List<Integer> lotto = lottoNumberGenerator.generate(PRICE);

    // then
		assertThat(lotto.stream().allMatch(v -> v >= FIRST_LOTTO_NUMBER && v <= LAST_LOTTO_NUMBER )).isTrue();
}

@DisplayName("로또 번호 범위 뒷번호 초과 테스트")
@Test
void lottoNumberRangeOverflowFailTest() {
    // given
    final LottoNumberGenerator lottoNumberGenerator = new LottoNumberGenerator();
    final int PRICE = 1000;
		final int FIRST_LOTTO_NUMBER = 1;
		final int OVERFLOWED_LAST_LOTTO_NUMBER = 46;

    // when
    final List<Integer> lotto = lottoNumberGenerator.generate(PRICE);

    // then
		assertThat(lotto.stream().allMatch(v -> v >= FIRST_LOTTO_NUMBER && v <= OVERFLOWED_LAST_LOTTO_NUMBER )).isFalse();
}

@DisplayName("로또 번호 범위 앞번호 초과 테스트")
@Test
void lottoNumberRangeUnderflowFailTest() {
    // given
    final LottoNumberGenerator lottoNumberGenerator = new LottoNumberGenerator();
    final int PRICE = 1000;
		final int UNDERFLOWED_FIRST_LOTTO_NUMBER = -1;
		final int LAST_LOTTO_NUMBER = 45;
		
    // when
    final List<Integer> lotto = lottoNumberGenerator.generate(PRICE);

    // then
		assertThat(lotto.stream().allMatch(v -> v >= UNDERFLOWED_FIRST_LOTTO_NUMBER && v <= LAST_LOTTO_NUMBER )).isFalse();
}

6. 테스트 실행 플랫폼에서 제공해주는 기능 활용

  • 중복적인 초기 데이터나 환경을 셋팅하기 위해 테스트 환경에 제공해주는 기능을 적극 활용한다.

7. 3rd Party에 대한 테스트 코드 작성 X

  • 3rd Party의 메서드는 모두 신뢰를 하고 테스트 코드를 작성하지 않는다.

참고

https://workshop.infograb.io/gitlab-ci/23add_test_stage_ci_pipeline/4_gitlab_ci-optimization/

단위 테스트 작성에 대한 모범 사례 - .NET

0개의 댓글