회사 API 테스트 모듈을 개발하다가 Functional Interface의 좋은 예가 있어서, 이를 통해 개발된 모듈이 어떻게 더욱 간결하고 효과적이게 작성될 수 있었는지를 공유하고자 합니다.
먼저, Functional Interface는 Java8에 도입된 인터페이스로 단 하나의 메서드만을 가지는 인터페이스를 의미합니다.
쉽게말해 인터페이스에 메서드가 하나만 있으면 Functional Interface 이죠.
test() 메서드를 만들어서 점진적으로 기능을 추가해보겠습니다.
우선 응답 결과가 성공인지 실패인지 확인하는 모듈을 보시죠.
protected static boolean test(Call call) {
try (Response response = call.execute()) {
if (!response.isSuccessful()) {
return false;
}
return true;
} catch (IOException e) {
return false;
}
}
먼저, Tester라는 클래스의 test() 메서드를 살펴보겠습니다. 이 메서드는 okhttp3 라이브러리를 사용하여 HTTP 요청을 보내는 역할을 합니다. 요청이 성공하면 true를, 실패하면 false를 반환합니다.
그런데, 여기서 문제가 발생합니다. 만약 API마다 테스트 방법이 다르다면 어떻게 해야할까요? 예를 들어,
- response body를 검증하는 테스트
- 파일의 크기를 검증하는 테스트
- 파일의 이름을 검증하는 테스트 등
위와 같은 다양한 시나리오가 존재한다면, 각 테스트마다 각각의 test() 메서드를 만들어서 적용해야 할 것입니다.
하지만, 이를 하나의 test() 메서드에서 처리하는 방법이 있으며 Functional Interface를 사용하는 겁니다.
@FunctionalInterface // 해당 어노테이션은 필수X
public interface Validator {
boolean proceed(Response body);
}
인터페이스에 proceed() 라는 메서드만 만들었습니다.
@FunctionalInterface : 이 어노테이션은 해당 인터페이스가 정확히 하나의 추상 메서드를 가지고 있어야 함을 명시합니다.
이 인터페이스를 test() 메서드에 적용해보죠.
protected static boolean test(Call call, Validator validator) {
try (Response response = call.execute()) {
if (!response.isSuccessful()) {
return false;
}
return validator.proceed(response); // 코드 추가
} catch (IOException e) {
return false;
}
}
아래 어떠한 기능도 추가하지 않고 test() 메서드를 완성시켰습니다.
- response body를 테스트 해야하는 api 테스트
- 파일 크기가 (response length) 원하는 범위 안에 있는지 확인 해야하는 api 테스트
- 파일 생성 날짜를 확인해야 하는 api 테스트
그럼 해당 검증 기능들은 test() 메서드를 사용하는 시점에 추가합니다.
test() 메서드를 사용하는 코드를 보시죠.
private static boolean naverMainPageTest() {
String url = "https://www.naver.com";
Call call = ConnectionManager.createCall(url, "GET", null);
return Tester.test(call, new Validator() {
@Override
public boolean proceed(Response response) throws IOException {
return response.body().string().contains("뉴스");
}
});
}
네이버 메인페이지로 접근하여 response body를 이용해 "뉴스" 라는 키워드가 존재하는지 확인하는 테스트 메서드가 만들어졌습니다.
람다 표현식으로 바꿔볼까요?
private static boolean naverMainPageTest() {
String url = "https://www.naver.com";
Call call = ConnectionManager.createCall(url, "GET", null);
return Tester.test(call, response -> response.body().string().contains("뉴스"));
}
아주 간단한 테스터기가 만들어졌죠?
이제 복잡한 테스트 코드를 만들어보면
private static boolean naverMainPageTest() {
String url = "테스트를 진행할 url";
Call call = ConnectionManager.createCall(url, "GET", null);
return Tester.test(call, response -> {
boolean keyword = Tester.keywordValidate(response, "뉴스");
boolean date = Tester.dateValidate(response, "2020-01-01");
boolean size = Tester.sizeValidate(response);
return keyword && date && size;
});
}
"키워드", "날짜", "size"를 검증하는 로직이 만들어졌습니다.
이렇게 함으로써 새로운 검증 로직이 필요한 경우 기존 test() 코드를 수정하지 않고
새로운 Validator 클래스를 추가하여 확장할 수 있습니다.
'Java & Kotlin' 카테고리의 다른 글
자바의 Garbage Collection 이해와 성능 최적화 방법 (0) | 2023.07.30 |
---|---|
Lock과 Double Check Lock: 동시성 문제 해결을 위한 효율적 패턴 (0) | 2023.07.11 |
Stack Overflow Error 발생 원인과 해결 방법: 재귀 호출 피하기 (0) | 2023.05.15 |
Java에서 제공해주는 Functional Interface (0) | 2023.04.30 |
메서드 재실행 어노테이션을 만들자. 실무 & @Retryable (0) | 2023.04.14 |