Spring

Test 1편 - Junit4 테스트

1. 개발 환경 Setting

Gradle Dependency

plugins {
    java
}

dependencies {
    testImplementation('junit:junit:4.13')
}
  • Junit4를 사용하기 위해 build.gradle에 위의 라인을 추가합니다.

Make Test Class

 

  • Test 하고자 하는 class 이름 오른쪽 클릭 → Go To → Test (Ctrl+Shift+T) → Test

 

 

  • Create Test → public, protected Method 만 보입니다.

 


2. JUnit 단위 테스트 작성 Example

JUnit Test

originalText를 STimestamp라는 포맷으로 변환해주는 클래스입니다. convertFormat()이라는 함수가 보이시나요? 개발자가 의도한 행동을 수행하는지 테스트해봐야 할 것 같은 느낌이 듭니다.

 

  • YearMonthDay가 하는 일 : "String → STimestamp" based on UTC
public class YearMonthDay implements Converter {

  private final String originalText;
  private final String zoneId;

  public YearMonthDay(String zoneId, String originalText) {
    this.zoneId = zoneId;
    this.originalText = originalText;
  }

  @Override
  public STimestamp convertFormat() {
    String yearMonthDayFormat = DateTimeFormat.YEARMONTHDAY.getFormat();
    LocalDate localDate = LocalDate
        .parse(originalText, DateTimeFormatter.ofPattern(yearMonthDayFormat));

    ZonedDateTime zonedDateTimeOf = ZonedDateTime.of(localDate.atStartOfDay(), ZoneId.of(zoneId));
    ZonedDateTime zonedDateTime = zonedDateTimeOf.withZoneSameInstant(ZoneId.of("UTC"));

    STimestamp result = new STimestamp();
    result.setTimestamp(Timestamp.valueOf(zonedDateTime.toLocalDateTime()));
    return result;
  }
}

3가지 정도를 Test 하면 함수에 대한 확신을 얻을 수 있을 것 같습니다.

  1.  zoneId가 Seoul일 때 UTC로 잘 변환해주는지!
  2.  zoneId가 UTC일 때 그대로 UTC로 반환해주는지!
  3.  format이 맞지 않는 String을 변환하려 할 때 의도한 오류가 검출되는지!

 

  • Test 전 setUp
public class YearMonthDayTest {

  private String yyyyMMdd;

  @Before
  public void setUp() {
    yyyyMMdd = "2020.05.13";
  }

  @Rule
  public ExpectedException thrown = ExpectedException.none();

    ...
}
  1.  @Before 어노테이션을 통해 각 @Test 전 수행하는 기본값을 만들어줍니다.
  2.  @Rule 어노테이션을 통해 Exception을 직접 확인합니다.

 


@Rule

  • Rule은 테스트 클래스에서 동작 방식을 재정의 하거나 쉽게 추가하는 것을 가능하게 합니다.
  • 사용자는 기존의 Rule을 재사용하거나 확장하는 것이 가능합니다.

ExpectedException

  • 예외를 직접 확인할 수 있습니다.
  • Error 메시지도 검증이 가능합니다.

 

  • zoneId가 Seoul일 때 UTC로 잘 변환해주는지 Test
@Test
public void convertFormat_Generates_UTC_STimestamp_If_ZoneId_Is_Seoul_Test() {
    //given
    String zoneIdSeoul = "Asia/Seoul";
    YearMonthDay yearMonthDaySeoul = new YearMonthDay(zoneIdSeoul, yyyyMMdd);
    Timestamp expected = Timestamp.valueOf("2020-05-12 15:00:00"); //24-9 = 15(UTC)

    //when
    Timestamp actual = yearMonthDaySeoul.convertFormat().getTimestamp();

    //then
    assertThat(actual, is(expected));
}

zoneId가 Seoul인 서버에서 받은 값 “2020.05.13”의 UTC값은 9시간을 뺀 “2020-05-12 15:00:00”입니다. 함수가 의도한 대로 9시간을 빼는 행동을 수행하는지 확인합니다.

 

  • zoneId가 UTC일 때 그대로 UTC 반환하는지 Test
@Test
public void convertFormat_Generates_UTC_STimestamp_When_ZoneId_Is_UTC_Test() {
    //given
    String zoneIdUTC = "UTC";
    YearMonthDay yearMonthDayUTC = new YearMonthDay(zoneIdUTC, yyyyMMdd);
    Timestamp expected = Timestamp.valueOf("2020-05-13 00:00:00");

    //when
    Timestamp result = yearMonthDayUTC.convertFormat().getTimestamp();

    //then
    assertThat(result, is(expected));
}

zoneId가 UTC인 서버에서 받은 값 “2020.05.13”은 함수가 의도한 대로 “2020-05-13 00:00:00으로 변환하는 행동을 수행하는지 확인합니다.

 

  • format이 맞지 않는 String을 변환하려 할 때 적절한 오류가 검출되는지 Test
@Test
public void convertFormat_Generates_Exception_If_Unexpected_Format_Test() {
    thrown.expect(DateTimeParseException.class);
    String unexpectedText = "unexpectedFormat";
    YearMonthDay yearMonthDayFail = new YearMonthDay("UTC", unexpectedText);

    yearMonthDayFail.convertFormat();
}

예상한 Format이 아닐 때 DateTimeParseException을 던지는지 확인합니다.

 


3. Hamcrest

가독성 있는 Test를 만들기 위해 Hamcrest 사용을 추천합니다. Hamcrest는 다양한 조건의 Match rule을 쉽게 작성하고, 테스트할 수 있는 라이브러리입니다.

 

assertEquals(actual, expected) 

assertThat(actual, is(expected))

위의 두 문장 중 가독성 측면에서 Hamcrest ‘is’를 사용한 아래쪽 문장이 더 가독성이 높습니다.

(더 많은 Matchers : http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/Matchers.html)

 

FailTest실행결과

또한, assertEquals와 달리 assertThat은 expected 값과 actual 값 모두 에러 메시지에 반환됩니다. 원인을 찾기 위해 별도의 디버깅 필요 없이 에러 메시지 만으로 잘못된 부분을 바로 파악할 수 있습니다.

 


4. Summary

Test 하고 싶은 행동을 정의합니다.

그 결과를 예상합니다.

예상 결과와 실제 함수의 return값을 비교합니다.

예상한 오류(Exception)인지 확인합니다.

 

Tip

  • given → when → then에 맞춰 작성하는 것이 좋습니다.
  • assert문은 한 test함수에 하나면 충분합니다.
  • verify로 수행 횟수를 체크할 수 있습니다.