토비의 스프링 6
Search
오브젝트와 의존관계

Spring 개념, 이론
•
관심사의 분리 (Separation of Concerns - SoC)
•
메소드 추출 - option + command + m
•
참고정보

관심사의 분리
•
하나의 클래스 안에 두 가지 관심사가 여전히 남아있다는 문제를 확인한 이후 좀 더 유연하게 확장하여Payment 서비스 자체는 재사용이 가능하도록 개선하는 작업을 수행

상속을 통한 확장
•
상속을 통해서 클래스 계층 구조 하나를 유지하는 게 아니고 클래스를 안전하게 분리시킨다.
•
Payment를 준비하는 클래스와 환율 정보를 가져오는 클래스를 완전히 독립적인 클래스로 만든다.

클래스의 분리


인터페이스 도입


관계설정 책임의 분리


오브젝트 팩토리
•
개방 폐쇄 원칙(Open-Closed Principle-OCP)
•
높은 응집도와 낮은 결합도(High Coherence and low coupling)
•
전략 패턴
package tobyspring.hellospring;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Sort {
public static void main(String[] args) {
List<String> scores = Arrays.asList("z", "b", "spring", "java");
Collections.sort(scores, (o1, o2) -> o1.length() - o2.length());
scores.forEach(System.out::println);
}
}

원칙과 패턴


스프링 컨테이너와 의존관계 주입 (DI)


싱글톤 레지스트리


DI와 디자인 패턴


의존성 역전 원칙
수동 테스트의 한계
•
프린트된 메시지를 수동으로 확인하는 방법은 불편하다.
•
사용자 웹 UI까지 개발한 뒤에 확인하는 방법은 테스트가 실패했을 때
확인할 코드가 많다.
•
테스트할 대상이 많아질 수록 검증하는데 시간이 많이 걸리고 부정확함.
작은 크기의 자동 수행되는 테스트(Automated Test)
•
개발자가 만드는 테스트 - 매우 중요!

자동으로 수행되는 테스트
•
JUnit 5

JUnit 테스트 작성


PaymentService 테스트


PaymentService 테스트 문제점


테스트와 DI (1)
수동 DI를 이용하는 테스트
•
테스트용 협력자(Collaborator)/의존 오브젝트를 테스트 대상에 직접 주입하고 테스트
스프링 DI를 이용하는 테스트
•
테스트용 협력자(Collaborator)/의존 오브젝트를 스프링의 구성 정보를 이용해서 지정하고 컨테이너로부터 테스트 대상을 가져와서 테스트

테스트와 DI (2)


학습 테스트(Learning Test)
package tobyspring.hellospring.payment;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.Clock;
import java.time.LocalDateTime;
import static java.math.BigDecimal.valueOf;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = TestPaymentConfig.class)
class PaymentServiceSpringTest {
// 의존 관계 주입
@Autowired
PaymentService paymentService;
@Autowired
Clock clock;
@Autowired
ExRateProviderStub exRateProviderStub;
@Test
void convertedAmount() throws IOException {
// exRate: 1000
Payment payment = paymentService.prepare(1L, "USD", BigDecimal.TEN);
assertThat(payment.getExRate()).isEqualByComparingTo(valueOf(1_000));
assertThat(payment.getConvertedAmount()).isEqualByComparingTo(valueOf(10_000));
// exRate: 500
exRateProviderStub.setExRate(valueOf(500));
Payment payment2 = paymentService.prepare(1L, "USD", BigDecimal.TEN);
assertThat(payment2.getExRate()).isEqualByComparingTo(valueOf(500));
assertThat(payment2.getConvertedAmount()).isEqualByComparingTo(valueOf(5_000));
}
@Test
void validUntil() throws IOException {
// 실행
Payment payment = paymentService.prepare(1L, "USD", BigDecimal.TEN);
// valid until이 prepare() 30분 뒤로 설정됐는가?
LocalDateTime now = LocalDateTime.now(this.clock);
LocalDateTime expectedValidUntil = now.plusMinutes(30);
Assertions.assertThat(payment.getValidUntil()).isEqualTo(expectedValidUntil);
}
}

Clock을 이용한 시간 테스트
•
도메인 모델 아키텍처 패턴
정리 - 개발자가 만드는 테스트
•
개발한 코드에 대한 검증 기능을 코드로 작성
•
자동으로 테스트를 수행하고 결과를 확인
•
테스팅 프레임워크를 활용
•
테스트 작성과 실행도 개발 과정의 일부

도메인 오브젝트 테스트
•
개방 폐쇄 원칙(OCP)
•
템플릿

다시 보는 개방 폐쇄 원칙
•
리팩터링의 전제 - 버그가 생기면 안 된다!
•
리팩터링을 잘하려면 테스트를 잘 만들어주어야 한다!

WebApiExRateProvider 리팩터링


변하는 코드 분리하기 - 메소드 추출
•
템플릿(Template)
•
템플릿 메소드 패턴?
•
메소드 추출 후 코드
package tobyspring.hellospring.exrate;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import tobyspring.hellospring.payment.ExRateProvider;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.stream.Collectors;
public class WebApiExRateProvider implements ExRateProvider {
@Override
public BigDecimal getExRate(String currency) {
String url = "https://open.er-api.com/v6/latest/" + currency;
return runApiForExRate(url);
}
private static BigDecimal runApiForExRate(String url) {
URI uri; // URL -> URI 변경
try {
uri = new URI(url);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
String response;
try {
response = executeApi(uri);
} catch (IOException e) {
throw new RuntimeException(e);
}
// build gradle에서 의존성 추가
// 'com.fasterxml.jackson.core:jackson-databind:2.12.5'
try {
return extractExRate(response);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
private static BigDecimal extractExRate(String response) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
ExRateData data = mapper.readValue(response, ExRateData.class);
return data.rates().get("KRW");
}
private static String executeApi(URI uri) throws IOException {
String response;
HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection();
try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
response = br.lines().collect(Collectors.joining());
}
return response;
}
}

변하지 않는 코드 분리하기 - 메소드 추출 (템플릿의 탄생)
Load more





