티스토리 뷰
목차
배경
Java 17버전부터는 Sealed Class와 Sealed Ineface를 제공한다. Sealed Class와 Sealed Interface는 상속과 구현할 클래스를 제한하여 구현 가능한 타입을 명확히 지정할 수 있다. 또한 문법적으로 상속/구현 가능한 클래스를 제한하여 의도치않은 상속과 구현을 방지할 수 있다. 이러한 특징으로 컴파일 시점에 올바르게 클래스를 상속 또는 인터페이스를 구현했는지 검증할 수 있다.
또한 Sealed Class를 활용하여 객체지향적 설계와 코드를 작성하고, Mocking을 통해 테스트 코드로 여러 케이스에 대한 검증을 할 수 있다.
예시 - Sealed Class를 활용한 API 응답 처리
예를 들어 API 요청과 응답에 따른 처리가 필요하다고 가정해보자. API 응답은 보통 두 개의 타입(성공과 실패)으로 제한된다. 이처럼 타입이 특정 케이스로 제한되는 경우 Sealed Class/Inteface를 적용하기 적합하다.
이 경우 다음과 같이 코드를 작성할 수 있다. API 응답 상태를 나타내기 위해 ApiResponse라는 이름의 Sealed Interface를 구현한다. 이 인터페이스는 성공(Success)과 실패(Failure)로만 구현할 수 있다.
public sealed interface ApiResponse permits Failure, Success {
default boolean isSuccess() {
return false;
}
default boolean isFailure() {
return false;
}
}
ApiResponse를 구현하는 클래스는 두 개의 메서드를 오버라이딩한다. 바로 API 요청의 성공과 실패 여부이다. 이 두 메서드(isSuccess, isFailure)는 default 메서드(Java 8부터 제공)로 구현하여 기본적으로 false를 반환하도록 한다.
성공(Success)과 실패(Failure) 구체 클래스의 두 메서드를 오버라이디한다. 성공 클래스는 isSuccess를 true로, 실패 클래스는 isFailure를 true로 반환한다.
public record Success<T>(T content) implements ApiResponse {
@Override
public boolean isSuccess() {
return true;
}
}
public record Failure(String message, int errorCode) implements ApiResponse {
@Override
public boolean isFailure() {
return true;
}
}
객체에게 메시지를 던지는 구조
이처럼 Sealed Class로 타입을 제한하고 객체에게 isSuccess, isFailure로 메서드를 던지는 구조는 객체지향적 설계와 개발로 이어질 수 있다. 예를 들어 API 요청과 응답에 따른 처리가 각각 다른 상황이라고 가정하자.
이 경우 API의 응답 코드나 응답 메시지를 직접 꺼내서 비교하는게 아니라 isSuccess, isFailure 메서드를 호출하여 객체에게 메시지를 던지는 구조로 설계할 수 있다. 이러한 구조는 명확한 의도로 가독성을 높일 수 있다.
if (response.isFailure()) {
throw new ApiCallException("API 호출 실패");
}
Mocking으로 API 호출 응답에 따른 테스트 검증
Sealed Class와 Inteface를 활용하면 Mocking을 통해 다양한 케이스에 대해 테스트 코드로 검증할 수 있다. 만약 API 호출 성공과 실패에 따른 로직을 테스트 코드로 검증하고 싶다면 다음과 같이 Mocking을 통해 검증할 수 있다.
// API Mocking
given(service.method(any(), any()))
.willReturn(
new Failure("API 호출 실패", HttpStatus.BAD_REQUEST.value())
);
API 호출 실패시 예외가 발생하도록 프로덕션 코드를 작성했다면, 다음과 같이 실패 케이스에 대해 모킹을 통해 테스트 코드로 검증을 할 수 있다.
@Test
void API_호출_실패_테스트() {
// given
given(apiCallService.method(any(), any()))
.willReturn(new Failure("API 호출 실패", HttpStatus.BAD_REQUEST.value()));
// when, then
final var request = new Request(...);
assertThatThrownBy(() -> service.run(request))
.isInstanceOf(ApiCallException.class);
}
'Java & Kotlin' 카테고리의 다른 글
[Kotlin] 코틀린의 인라인 클래스 (Inline value classes) (2) | 2024.12.09 |
---|---|
[Kotlin] check()와 require(), 코틀린 람다 표현식 {} (0) | 2024.12.01 |
[JVM] JVM 기반언어, 자바와 코틀린의 빌드 과정과 실행 원리 (1) | 2024.11.24 |
[Java] 자바의 두가지 정렬 방법(Comparable, Comparator) (0) | 2024.08.01 |
[Java] 주요 함수형 인터페이스(Function Interface) (2/2) (0) | 2024.03.19 |