테스트
목표: 작은 크기의 자동 수행되는 테스트를 작성한다 (개발자가 만드는 테스트)
- 개발한 코드에 대한 검증 기능을 코드로 작성한다
- 자동으로 테스트를 수행하고 결과를 확인한다
- 테스팅 프레임워크를 활용한다
- 테스트 작성과 실행도 개발 과정의 일부이다
Junit 테스트
@Test
- 테스트 메서드@BeforeEach
- 테스트 메서드가 실행되기 전에 실행된다- 테스트마다 새로운 인스턴스가 생성된다
테스트의 구성요소
- 테스트 대상 (SUT, System Under Test)
ex) PaymentService
- 테스트
- 협력자: 다른 오브젝트와 관계를 맺고 기능을 이용
ex) WebAPIExRateProvider
- Stub용 오브젝트(Imposter, Test Double): 우리가 원하는 환율값을 임의로 반환해줌
테스트와 DI
현재 테스트의 문제점
- 우리가 제어할 수 없는 외부 시스템에 문제가 생기면?
- ExRateProvider가 제공하는 환율 값으로 계산한 것인가?
- 환율 유효 시간이 정확한 것인가?
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를 이용하여 원하는 결과를 돌려준다
도메인 오브젝트 테스트
도메인 모델 아키텍처 패턴
도메인 로직, 비즈니스 로직을 어디에 둘 지 결정하는 패턴
- 트랜잭션 스크립트 - 서비스 메소드 (PaymentService.prepare)
- 도메인 모델 - 도메인 모델 오브젝트 (Payment)
도메인 모델 아키텍처의 장점
- 확장성 좋음 (도메인과 관련된 내용을 하나로 모을 수 있기 때문)
- 테스트 관점에서 좋음