인프런 커뮤니티 질문&답변

hooni님의 프로필 이미지

작성한 질문수

토비의 스프링 6 - 이해와 원리

도메인 오브젝트 테스트 강의 개선 과제

해결된 질문

작성

·

137

·

수정됨

0

안녕하세요.

섹션3 마지막 도메인 오브젝트 테스트에서 한 줄로 줄이기 해보라고 하셨는데, 제 생각이 맞는건지 코멘트 부탁드리겠습니다.

@Component
public class PaymentService {

    private final ExRateProvider provider;
    //private final Clock clock;

    public PaymentService(ExRateProvider provider/*, Clock clock*/) {
        this.provider = provider;
        //this.clock = clock;
    }

    public Payment prepare(Long orderId, String currency , BigDecimal foreignCurrencyAmount) throws IOException {
        return Payment.createPrepared(orderId, currency, foreignCurrencyAmount, this.provider);
    }

}
static Payment createPrepared(Long orderId, String currency, BigDecimal foreignCurrencyAmount, ExRateProvider exRateProvider) throws IOException {

   BigDecimal exchangeRate = exRateProvider.getExRate(currency);
   BigDecimal convertedAmount = foreignCurrencyAmount.multiply(exchangeRate);
   LocalDateTime validUntil = LocalDateTime.now(Clock.fixed(Instant.now(), ZoneId.systemDefault())).plusMinutes(30);

return new Payment(orderId, currency, foreignCurrencyAmount, exchangeRate, convertedAmount, validUntil);
}

웹환경 기준으로는 사용자 각각의 요청마다 payment가 달라야하고 따라서 유효시간도 다를것이기 때문에, Payment 자체나 Clock.systemDefaltZone()을 바로 싱글톤한 Bean으로 등록하면 안될 것 같더라고요. (테스트돌려보니 다른 객체라도 무조건 시간 같게 나오는 것 확인) 이렇게 작성해도 또 다른 문제는 없는지 확인부탁드립니다.

 

 

답변 2

0

토비님의 프로필 이미지
토비
지식공유자

ExRateProvider를 이용해서 환율 정보를 가져오는 것도 Payment의 createPrepared()의 파라미터로 전달해서 내부에서 환율 정보를 가져오는 작업을 직접 하도록 만드셨네요. 이건 제가 의도했던 대로 잘하셨습니다.

Payment 오브젝트 밖에서는 이게 API를 사용할 수도 있고, DB 액세스를 이용할 수도 있지만 Payment는 그런 구현에 의존하지 않는 인터페이스만 가지고 있기 때문에 이를 통해서 필요한 정보를 요청해서 사용할 수 있게 됩니다. 메소드 주입 방식을 잘 활용한 경우입니다. 이러면 도메인 오브젝트 테스트를 작성하기도 더 편리해집니다.

그런데 createPrepared() 내부에 Clock.fixed()를 넣으신 것은 개선이 필요합니다. 제가 의도했던 방법은 Clock 타입의 파라미터를 createPrepared()에 추가하는 것입니다. 내부에선 LocalDateTime.now(clock) 이렇게만 사용하면 되겠죠. 어떤 Clock을 전달하는지는 Payment 오브젝트 외부에서 결정해줘야 합니다.

이 부분도 개선해서 다시 코드를 보여주시면 제가 다시 의견 드릴게요.

hooni님의 프로필 이미지
hooni
질문자

@Component
public class PaymentService {

    private final ExRateProvider provider;
    private final Clock clock;

    public PaymentService(ExRateProvider provider, Clock clock) {
        this.provider = provider;
        this.clock = clock;
    }

    public Payment prepare(Long orderId, String currency , BigDecimal foreignCurrencyAmount) throws IOException {
        return Payment.createPrepared(orderId, currency, foreignCurrencyAmount, this.provider, this.clock);
    }
}
static Payment createPrepared(Long orderId, String currency, BigDecimal foreignCurrencyAmount,
                                  ExRateProvider exRateProvider, Clock clock) throws IOException {
        BigDecimal exchangeRate = exRateProvider.getExRate(currency);
        BigDecimal convertedAmount = foreignCurrencyAmount.multiply(exchangeRate);
        LocalDateTime validUntil = LocalDateTime.now(clock).plusMinutes(30);

        return new Payment(orderId, currency, foreignCurrencyAmount, exchangeRate, convertedAmount, validUntil);
    }

이해했습니다. 이런 식으로 만들면 clock까지 테스트해볼 수 있는 도메인 모델이 되니까 테스트코드쪽에서 PaymentServiceprepare메소드가 아니라 Payment.createPrepared를 통해서 간편하게 테스트가 가능하게도 바뀐것같네요

 

토비님의 프로필 이미지
토비
지식공유자

잘 작성하셨습니다. 🙂

0

안녕하세요, 인프런 AI 인턴입니다.

hooni님의 코드 리뷰 요청에 대해 답변 드립니다.

주신 코드를 보면, Payment 객체 생성 시 LocalDateTime.now(Clock.fixed(Instant.now(), ZoneId.systemDefault())).plusMinutes(30)을 사용하여 각 Payment의 유효 시간을 설정하고 있는 부분이 눈에 띕니다. 이렇게 작성하신 부분에 대해 고민하셨던 것처럼, 실제로 웹 애플리케이션 환경에서는 각 사용자의 요청마다 독립된 Payment 인스턴스와 유효 시간 설정이 필요합니다.

Clock을 싱글턴으로 사용하는 것에 대한 우려는 타당합니다. 싱글턴 인스턴스로 Clock을 사용하면 애플리케이션 전역에서 같은 시각 정보를 공유하게 되어, 사용자별 요청 시각이 다름에도 불구하고 모든 Payment가 같은 유효 시간을 갖게 됩니다. 따라서, 각 요청의 시각에 독립적으로 유효 시간을 처리해야 합니다.

Clock.systemDefaultZone()를 직접 사용하는 대신, PaymentService 생성자를 통해 Clock을 주입받는 방식으로 변경하면 테스트 환경에서의 유연성이 증가하고, 실제 운영 환경에서는 기본 시스템 시간대 혹은 다른 Clock 전략을 사용할 수 있는 옵션을 제공할 수 있습니다.

public class PaymentService {
    private final ExRateProvider provider;
    private final Clock clock;

    public PaymentService(ExRateProvider provider, Clock clock) {
        this.provider = provider;
        this.clock = clock;
    }

    public Payment prepare(Long orderId, String currency , BigDecimal foreignCurrencyAmount) throws IOException {
        LocalDateTime validUntil = LocalDateTime.now(clock).plusMinutes(30);
        return Payment.createPrepared(orderId, currency, foreignCurrencyAmount, this.provider, validUntil);
    }
}

위와 같이 Clock을 사용하는 방법은 테스트 가능성을 높이고, 코드의 유연성을 증가시킵니다. 테스트 시에는 고정된 시각을 반환하는 Clock 인스턴스를 주입하여 시간에 대한 검증을 용이하게 할 수 있습니다.

hooni님의 접근 방식은 애플리케이션의 실제 요구 사항과 테스트의 용이성을 모두 고려한 좋은 시작점입니다. 필요에 따라서는 Clock 인스턴스의 범위(scope) 및 라이프사이클을 신중히 관리해야 하며, 이를 통해 시스템 시간과 관련된 다양한 요구 사항을 더 잘 처리할 수 있습니다.

hooni님의 프로필 이미지
hooni
질문자

감사합니다

hooni님의 프로필 이미지

작성한 질문수

질문하기