Study

@ParameterlizedTest

voider 2021. 9. 8. 21:46

@ParameterlizedTest 는 인자를 받아서 여러 번 테스트를 실행한다.

테스트를 진행하다 보면 거의 같고 약간만 다른 테스트 케이스를 만날 수 있다.

이를 테면 어떤 주차장에 방문 예약하는 테스트를 만든다고 가정하면 아래와 같은 예외 케이스들이 나온다.

  1. 핸드폰 번호가 없으면 BadRequestException 이 발생한다.
  2. 날짜가 잘못되면 BadRequestException 이 발생한다.
  3. 차량번호가 잘못되면 BadRequestException
  4. 비활성화된 주차장이라면 ForbbidenException

이런 예외들이 발생할 수 있다.

이걸 하나씩 테스트하면 테스트 코드가 거의 중복된다.

@Test
fun `방문차량 예약 시 주차장이 비활성화 상태면 ForbbidenException이 발생한다`() {
    // given
  val params = listOf(getParam())

  doReturn(주차장().apply{ 활성상태 = false }).`when`(parkingRepository).findById(any())

  // when
  assertThrows<ForbbidenException> { parkingService.reserve(params) }

  // then
  verify(parkingRepository, times(1)).findById(any())
}

@Test
fun `방문차량 예약 시 차량번호가 잘못된 형식이면 BadRequestException 발생한다`() {
    // given
  val params = listOf(getParam().apply { 차량번호 : "92록11" } )

  doReturn(주차장().apply{ 활성상태 = true }).`when`(주차장Repository).findById(any())

  // when
  assertThrows<BadRequestException> { parkingService.reserve(params) }

  // then
  verify(parkingRepository, times(1)).findById(any())
}

//... 다른 테스트도 시나리오가 같음

이럴 때 틀은 두고 달라지는 부분만 파라미터화해서 하나의 메소드를 반복 사용한다. 이것이 Parameterlized Test다.

parameterlized 테스트에는 여러 인수를 받을 수 있다. csv, Enum, primitive type, null... 그 중에는 좀더 다양한 인수를 받을 수 있는 @MethodSource 라는 게 있다.

@MethodSource

이 어노테이션은 외브 클래스 또는 내부 클래스에 있는 하나 이상의 팩토리 메소드를 참조할 수 있다.

  • 반드시 static 이어야 하고(Kotlin인 경우 companion object 내에 선언, @JvmStatic 붙여야 함.
  • 파라미터를 가질 수 없다.
  • 속성 값으로 팩토리 메소드 이름을 명시해야 한다. (@MethodSource(stringProvider) )

팩토리 메소드는 paramterlized 테스트에 넘길 인수 스트림을 생성해야 한다.

IntStream , Stream 등 모두 사용할 수 있고, 여러 매개 변수를 넘겨야 할 때는 Arguments.of() 를 사용할 수도 있다.

위에서 중복된 테스트를 파라미터화된 테스트로 변경하면 이렇게 할 수 있다.

@ParameterizedTest(name = "{index} : 방문차량 예약 시 {3}")
@MethodSource("reservationParameters")
fun `방문차량 예약 시 입력 값이 잘못되면 예외 발생한다`(zone: 주차장, param: Param, throwable: Class<Throwable>, displayName: String) {
    // given
    val params = listOf(param)

    doReturn(Optional.of(zone)).`when`(parkingRepository).findById(any())

    // when
    assertThrows(throwable) { parkingService.reserve(params) }

    // then
    verify(parkingRepository, times(1)).findById(any())
}

companion object {
        var parkingWhitelistCreateParam = Param(/*생성*/)

        @JvmStatic
        fun reservationParameters(): List<Arguments> {
            return listOf(
                Arguments.of(
                    주차장().apply { 활성상태 = false },
                    param, ForbiddenException::class.java,
                    "주차장 활성화 안 되어 있으면 ForbiddenException"),
                Arguments.of(
                    주차장(),
                    param.apply { 차량번호 = "1콩292" },
                    BadRequestException::class.java,
                    "유효하지 않은 차량번호 입력하는 경우 BadRequestException"
                ),
                Arguments.of(
                    주차장(),
                    param.apply {
                        예약시간 = LocalDateTime.now().plusHours(2)
                        예약종료시간 = LocalDateTime.now().minusHours(11)
                    },
                    BadRequestException::class.java,
                    "잘못된 시간에 신청하는 경우 BadRequestException"
                ),
                                //....
            )
        }
    }

@ParameterizedTest(name = "{index} : 방문차량 예약 시 {3}") 이걸 보면 알 수 있지만 {index} 형태로 몇 번째 테스트인지에 대한 index를 사용할 수 있다. {3}은 네 번째 매개변수를 의미한다. 이것을 displayName처럼 사용할 수 있다.

결론

테스트는 어렵다. holy shit

'Study' 카테고리의 다른 글

type check는 왜 필요한가?  (0) 2022.05.18
테스트 대역  (0) 2021.12.21
파일업로드 input[files] FileList 동적으로 변경하기  (11) 2021.06.12
[Database] Transaction, Lock  (0) 2021.05.25
Flyway  (0) 2021.05.16