Closed Leehyoungwoo closed 4 months ago
강의 코드
package tobyspring.hellospring.payment; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import tobyspring.hellospring.exrate.WebApiExRateProvider; import java.io.IOException; import java.math.BigDecimal; import java.time.LocalDateTime; import static org.assertj.core.api.Assertions.assertThat; class PaymentServiceTest { @Test @DisplayName("prepare 메서드가 요구사항 3가지를 잘 충족했는지 검증") void prepare() throws IOException { PaymentService paymentService = new PaymentService(new WebApiExRateProvider()); Payment payment = paymentService.prepare(1L, "USD", BigDecimal.TEN); // 환율정보 가져온다 assertThat(payment.getExRate()).isNotNull(); // 원화환산금액 계산 assertThat(payment.getConvertedAmount()) .isEqualTo(payment.getExRate().multiply(payment.getForeignCurrencyAmount())); //원화환산금액유효시간 계산 assertThat(payment.getValidUntil()).isAfter(LocalDateTime.now()); assertThat(payment.getValidUntil()).isBefore(LocalDateTime.now().plusMinutes(30)); } }
제어할 수 없는 외부 시스템에 문제가 생기면?
ExRateProvider가 제공하는 환율 값으로 계산한 것인가?
환율 유효 시간 계산은 정확한 것인가?
테스트 대상 (SUT)
테스트
협력자
BigDecimal은 유효자리수도 따지기 때문에 isEqualByComparingTo 로 비교하는게 나음
isEqualByComparingTo
@ContextConfiguration
@Autowired
도메인 로직, 비즈니스 로직을 어디에 둘지를 결정하는 패턴
URI 준비하고 예외처리를 위한 작업을 하는 코드
API를 실행하고, 서버로부터 받은 응답을 가져오는 코드
JSON 문자열을 파싱하고 필요한 환율 정보를 추출하는 코드
예시 코드
package tobyspring.hellospring.exrate; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.stereotype.Component; 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.net.URL; import java.util.stream.Collectors; @Component public class WebApiExRateProvider implements ExRateProvider { @Override public BigDecimal getExRate(String currency) { String url = "https://open.er-api.com/v6/latest/" + currency; URI uri; try { uri = new URI(url); } catch (URISyntaxException e) { throw new RuntimeException(e); } String response; try { // 2번 HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection(); try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { response = in.lines().collect(Collectors.joining()); } // 2번 끝 } catch (IOException e) { throw new RuntimeException(e); } try { // 3번 ObjectMapper mapper = new ObjectMapper(); ExRateData data = mapper.readValue(response, ExRateData.class); return data.rates().get("KRW"); // 3번 끝 } catch (JsonProcessingException e) { throw new RuntimeException(e); } } }
메서드 추출
package tobyspring.hellospring.exrate; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.stereotype.Component; 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; @Component 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; 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); } 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 in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { response = in.lines().collect(Collectors.joining()); } return response; } }
!https://user-images.githubusercontent.com/10750614/55551325-7fb4dd00-5715-11e9-96a7-23b8a0a8b103.png
테스트
수동 테스트의 한계
작은 크기의 자동 수행되는 테스트(Automated Test)
개발자가 만드는 테스트
JUnit5
PaymentService 테스트의 문제점
강의 코드
제어할 수 없는 외부 시스템에 문제가 생기면?
ExRateProvider가 제공하는 환율 값으로 계산한 것인가?
환율 유효 시간 계산은 정확한 것인가?
테스트의 구성 요소
테스트 대상 (SUT)
테스트
협력자
BigDecimal은 유효자리수도 따지기 때문에
isEqualByComparingTo
로 비교하는게 나음테스트와 DI
수동 DI를 이용하는 테스트
스프링 DI를 이용하는 테스트
@ContextConfiguration
,@Autowired
학습 테스트 (Learing Test)
도메인 오브젝트 테스트
도메인 모델 아키텍처 패턴
도메인 로직, 비즈니스 로직을 어디에 둘지를 결정하는 패턴
템플릿
스프링과 JDK 업그레이드
개방 폐쇄 원칙(OCP)
템플릿
WebApiExRateProvider Refactoring
변하는 코드 분리하기
WebApiExRateProvider의 구성
URI 준비하고 예외처리를 위한 작업을 하는 코드
API를 실행하고, 서버로부터 받은 응답을 가져오는 코드
JSON 문자열을 파싱하고 필요한 환율 정보를 추출하는 코드
예시 코드
메서드 추출
변하지 않는 코드 분리하기 - 템플릿
템플릿
템플릿 메서드 패턴
예시 코드
ApiExecutor 분리 - 인터페이스 도입과 클래스 분리
ApiExecutor 콜백과 메서드 주입 - Callback + Method Injection
콜백(Callback)
템플릿/콜백은 전략 패턴의 특별한 케이스
메서드 주입
템플릿/콜백의 작업 흐름
!https://user-images.githubusercontent.com/10750614/55551325-7fb4dd00-5715-11e9-96a7-23b8a0a8b103.png
ExRateExtractor 콜백 - Callback + Method Injection
ApiTemplacte 분리 - 환율 정보 API의 기본 틀
디폴트 콜백과 템플릿 빈 - 재사용 가능한 템플릿 빈
스프링이 제공하는 템플릿
RestTemplate
HTTP API 요청을 처리하는 템플릿
HTTP Client 기술을 사용해서 ClientHttpRequest를 생성하는 전략
doExecute()
스프링에의 Template
JpaTemplateMyBatis