반응형
목차
1. @Disabled
@Disabled는 JUnit에서 제공하는 테스트를 비활성화하는 기능을 가진 어노테이션입니다.
아래와 같이 사용 될 수 있습니다.
@Disabled
@Test
fun test() {
Assertions.fail<String>("not executed")
}
Disabled로 설정한 이유를 기입 할 수도 있습니다.
@Disabled("This is disabled because of a bug ticket BUG-12345")
@Test
fun test() {
Assertions.fail<String>("not executed")
}
2. 조건부로 Disabled 시켜야 하는 상황
@Disabled를 사용하면 아래와 같은 시나리오로 사용 할 수 있습니다.
- 특정 이슈로 인해 @Disabled 어노테이션을 붙여 테스트를 비활성화
- 이슈로 해소되면 @Disabled를 제거하여 커밋
위 같은 동작은 개발자가 직접 수작업 해줘야 하는 문제점이 있습니다.
또한 두개의 상황이 반복적으로 발생 한다면 테스트 코드 관리에 어려움이 있을 수 있습니다.
가령, 아래와 같은 이슈를 예시로 들어보겠습니다.
WebClient를 통해 외부 API호출이 포함된 테스트 케이스
사실 일반적이지는 않을 수 있습니다.
왜냐하면 외부 의존성이 테스트 코드에 포함되는 것은 때에 따라 부적절 할 수 있습니다.
여기서는 외부 API 호출이 가능하다면 테스트를 활성화하고, 불가능하다면 테스트를 비활성화 하는 샘플코드를 작성해 보겠습니다.
아래와 같은 어노테이션도 있습니다.
Os, SystemProperty, Env에 따라 테스트를 비활성 할 수 있습니다. 다만, 여기서는 다루지 않습니다.
- @DisabledOnOs
- @DisabledOnJre
- @DisabledIfSystemProperty
- @DisabledIfEnvironmentVariable
3. ExecutionCodition을 구현한 클래스 작성
class EnabledIfReachableCondition : ExecutionCondition {
override fun evaluateExecutionCondition(context: ExtensionContext): ConditionEvaluationResult {
val element = context.element.orElseThrow { IllegalStateException() }
return findAnnotation(element, EnabledIfReachable::class.java)
?.let { disableIfUnreachable(it, element) }
?: enabled("@EnabledIfReachable is not present")
}
private fun disableIfUnreachable(
annotation: EnabledIfReachable,
element: AnnotatedElement
): ConditionEvaluationResult {
val host = annotation.host
val port = annotation.port
val timeoutMillis = annotation.timeoutMillis
return try {
val reachable = pingHost(host, port, timeoutMillis)
if (reachable)
enabled(format("%s is enabled because %s is reachable", element, host))
else
disabled(
format(
"%s is disabled because %s could not be reached in %dms",
element, host, timeoutMillis
)
)
} catch (e: Exception) {
// 연결 시도 중 예외 발생 시 테스트를 disable 처리
disabled(
format(
"%s is disabled because %s check failed with: %s",
element, host, e.message ?: "unknown error"
)
)
}
}
private fun pingHost(host: String, port: Int, timeout: Int): Boolean {
try {
Socket().use { socket ->
socket.connect(InetSocketAddress(host, port), timeout)
return true
}
} catch (e: IOException) {
return false
}
}
}
ExecutionCondition 인터페이스를 구현한 새로운 클래스를 하나 만들어줬습니다.
주요 함수는 어노테이션의 속성값을 읽어 Socket 통신이 가능한지 여부를 파악합니다.
Socket 통신이 가능하다면 eval(enabled)
불가능 하다면 eval(disabled) 방식로 동작하게 됩니다.
4. 기능 확장을 위한 CustomAnnotation 생성
아래와 같이 커스텀 어노테이션을 작성 합니다.
@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@ExtendWith(EnabledIfReachableCondition::class)
annotation class EnabledIfReachable(
val host: String,
val port: Int,
val timeoutMillis: Int
)
5. 테스트 코드 작성
class WebClientUtilTest {
@EnabledIfReachable(
host = "localhost",
port = 8080,
timeoutMillis = 1000
)
@Test
fun `test web client`() {
val res = WebClientUtil
.get("http://localhost:8080/api/hello", mapOf("Content-Type" to "text/plain"))
.block()
Assertions.assertEquals("hello world!", res)
}
}
위와 같이 사용해줄 수 있습니다.
먼저 localhost:8080이 접근 가능할 때 실행 해봅시다.
그리고 localhost:8080에 접근이 불가 할때 실행 시켜 봅니다.
비활성화 된 테스트케이스를 확인 할 수 있습니다.
반응형
'개발 이야기 > Springboot' 카테고리의 다른 글
Webflux에서 switchIfEmpty가 예상과 다른 동작을... (3) | 2025.06.12 |
---|---|
JUnit 5, Mocking with ArgumentCaptor (0) | 2025.04.24 |
httpclient thread limit exceeded replacing blocked worker 에러 (1) | 2025.04.17 |
TestContainers에서 Can't get Docker image: RemoteDockerImage에러 발생 시 (0) | 2025.02.02 |
Springboot App 시작, 종료시 로직 실행 (0) | 2024.08.31 |