#스프링 #강의

[토비의 스프링 6] 테스트

1jeongg 1jeongg Follow 2024년 06월 29일 · 4 mins read
Share this

테스트

목표: 작은 크기의 자동 수행되는 테스트를 작성한다 (개발자가 만드는 테스트)

  • 개발한 코드에 대한 검증 기능을 코드로 작성한다
  • 자동으로 테스트를 수행하고 결과를 확인한다
  • 테스팅 프레임워크를 활용한다
  • 테스트 작성과 실행도 개발 과정의 일부이다

Junit 테스트

  • @Test - 테스트 메서드
  • @BeforeEach - 테스트 메서드가 실행되기 전에 실행된다
  • 테스트마다 새로운 인스턴스가 생성된다

테스트의 구성요소

  • 테스트 대상 (SUT, System Under Test)
    • ex) PaymentService
  • 테스트
  • 협력자: 다른 오브젝트와 관계를 맺고 기능을 이용
    • ex) WebAPIExRateProvider
    • Stub용 오브젝트(Imposter, Test Double): 우리가 원하는 환율값을 임의로 반환해줌

테스트와 DI

현재 테스트의 문제점

  1. 우리가 제어할 수 없는 외부 시스템에 문제가 생기면?
  2. ExRateProvider가 제공하는 환율 값으로 계산한 것인가?
  3. 환율 유효 시간이 정확한 것인가?
class PaymentServiceTest {

    /*
     * 현재 테스트의 문제점
     * 1. 우리가 제어할 수 없는 외부 시스템에 문제가 생기면?
     * 2. ExRateProvider가 제공하는 환율 값으로 계산한 것인가?
     * 3. 환율 유효 시간이 정확한 것인가?
     * 
     * 해결방안
     * 임의의 테스트 대역(Test double)인 Stub 활용하자
     */
    @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));
    }

}

Stub을 통한 외부 시스템 사용 최소화

class PaymentServiceTest {

  @Test
  @DisplayName("prepare 메소드가 요구사항 3가지를 잘 충족하는지 검증")
  void prepare() throws IOException {
    PaymentService paymentService = new PaymentService(new ExRateProviderStub(valueOf(500)));
    Payment payment = paymentService.prepare(1L, "USD", BigDecimal.TEN);
    
    assertThat(payment.getExRate()).isEqualByComparingTo(valueOf(500));
    assertThat(payment.getConvertedAmount()).isEqualByComparingTo(valueOf(5_000));
    assertThat(payment.getValidUntil()).isAfter(LocalDateTime.now());
    assertThat(payment.getValidUntil()).isBefore(LocalDateTime.now().plusMinutes(30));
  }

}

public class ExRateProviderStub implements ExRateProvider{

  private BigDecimal exRate;

  public ExRateProviderStub(BigDecimal exRate) {
    this.exRate = exRate;
  }

  ...

  @Override
  public BigDecimal getExRate(String currency) throws IOException {
    return exRate;
  }
}

스프링 DI를 이용하는 테스트

테스트용 협력자/의존 오브젝트를 스프링의 구성 정보를 이용해서 지정하고 컨테이너로부터 테스트 대상을 가져와서 테스트

@ContextConfiguration, @Autowired

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = TestObjectFactory.class)
class PaymentServiceSpringTest {

    @Autowired
    PaymentService paymentService;

    @Test
    @DisplayName("prepare 메소드가 요구사항 3가지를 잘 충족하는지 검증")
    void prepare() throws IOException {
        Payment payment = paymentService.prepare(1L, "USD", BigDecimal.TEN);
        assertThat(payment.getExRate()).isEqualByComparingTo(valueOf(1_000));
        assertThat(payment.getConvertedAmount()).isEqualByComparingTo(valueOf(10_000));
    }
}

@Configuration
@ComponentScan
public class TestObjectFactory {

    @Bean
    public PaymentService paymentService() {
        return new PaymentService(exRateProvider());
    }

    @Bean
    public ExRateProvider exRateProvider() {
        return new ExRateProviderStub(BigDecimal.valueOf(1_000));
    }
}

학습 테스트

직접 만들지 않은 코드, 라이브러리, 레거시 시스템에 대한 테스트

테스트 대상의 사용방법을 익히고 동작방식을 확인하는데 유용하다. 외부 기술, 서비스가 버전이 올라갔을 때 이전과 동일하게 동작하는지 확인할 수 있다.

FixedClocK: 유효 시간 등을 검증할 때 미리 정해진 시각을 만들어 둠다. 테스트에서 DI를 이용하여 원하는 결과를 돌려준다

도메인 오브젝트 테스트

도메인 모델 아키텍처 패턴
도메인 로직, 비즈니스 로직을 어디에 둘 지 결정하는 패턴

  1. 트랜잭션 스크립트 - 서비스 메소드 (PaymentService.prepare)
  2. 도메인 모델 - 도메인 모델 오브젝트 (Payment)

도메인 모델 아키텍처의 장점

  • 확장성 좋음 (도메인과 관련된 내용을 하나로 모을 수 있기 때문)
  • 테스트 관점에서 좋음


1jeongg
Written by 1jeongg Follow

I'm studying Android development by Kotlin and Spring by Java