강의

멘토링

커뮤니티

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

addvd님의 프로필 이미지
addvd

작성한 질문수

Practical Testing: 실용적인 테스트 가이드

Mockito로 Stubbing하기

Mockito로 Stubbing하기 8분경 OrderRepository 테스트

해결된 질문

작성

·

125

0

  • 학습 관련 질문을 남겨주세요. 어떤 부분이 고민인지, 무엇이 문제인지 상세히 작성하면 더 좋아요!

  • 먼저 유사한 질문이 있었는지 검색해 보세요.

  • 서로 예의를 지키며 존중하는 문화를 만들어가요.

     

안녕하세요 강의를 보다가 OrderRepository에 대한 테스트는 직접 짜보라는 말씀을 듣고 직접 한번 작성을 해봤습니다.

@ActiveProfiles("test")
@DataJpaTest
class OrderRepositoryTest {

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private ProductRepository productRepository;

    @DisplayName("원하는 주문 상태를 가진 주문들을 조회한다.")
    @Test
    void findOrdersBy() {
        // given
        Product product1 = createProduct("001", HANDMADE, SELLING, "아메리카노", 4000);
        Product product2 = createProduct("002", HANDMADE, HOLD, "카페라떼", 4500);
        Product product3 = createProduct("003", HANDMADE, STOP_SELLING, "팥빙수", 7000);
        productRepository.saveAll(List.of(product1, product2, product3));

        Order order1 = createOrderWithOrderStatus(List.of(product1, product2), LocalDateTime.now(), PAYMENT_COMPLETED);
        Order order2 = createOrderWithOrderStatus(List.of(product3), LocalDateTime.now().minusDays(1), PAYMENT_COMPLETED);
        Order order3 = createOrderWithOrderStatus(List.of(product1, product2, product3), LocalDateTime.now().plusDays(1), PAYMENT_COMPLETED);
        orderRepository.saveAll(List.of(order1, order2, order3));

        // when
        List<Order> orders = orderRepository.findOrdersBy(
                LocalDateTime.now().minusDays(1),
                LocalDateTime.now().plusDays(1),
                PAYMENT_COMPLETED
        );

        // then
        assertThat(orders).hasSize(2)
                .extracting("orderStatus", "totalPrice", "registeredDateTime")
                .containsExactlyInAnyOrder(
                        tuple(PAYMENT_COMPLETED, 8500, order1.getRegisteredDateTime()),
                        tuple(PAYMENT_COMPLETED, 15500, order3.getRegisteredDateTime())
                );
    }

    private Order createOrder(List<Product> products, LocalDateTime registeredDateTime) {
        return Order.create(products, registeredDateTime);
    }

    private Order createOrderWithOrderStatus(List<Product> products, LocalDateTime registeredDateTime, OrderStatus status) {
        Order order = createOrder(products, registeredDateTime);
        order.updateOrderStatus(status);
        return order;
    }

    private Product createProduct(String productNumber, ProductType productType, ProductSellingStatus sellingStatus, String name, int price) {
        return Product.builder()
                .productNumber(productNumber)
                .type(productType)
                .sellingStatus(sellingStatus)
                .name(name)
                .price(price)
                .build();
    }

}

이런 식으로 작성을 하였는데 제가 궁금한 점은

  1. createProduct는 대부분의 테스트 클래스에서 사용되는데 하나의 static 클래스와 같이 외부로 분리하는게 좋지 않을까 라는 생각을 해봤습니다.

  2. 저는 Order 클래스의 상태 변경을 위해서 updateOrderStatus라는 메소드를 생성 하였는데 (아직 해당 강의를 끝까지는 보지 않고 강의를 보기전에 먼저 테스트 코드를 작성 해보고 있습니다) 이런 식으로 테스트를 위해서 메소드를 생성 하는 경우도 괜찮을까요?

  3. 제가 작성한 코드의 given 절이 너무 길어지는 느낌을 받았습니다. 현재는 order와 product를 생성 하는 정도이지만 더 복잡한 로직을 생성 할 경우 given이 너무 길어 지는것에 대한 해결 방안이 있을까요?

     

최대한 구글을 사용하여 찾아는 보았지만 우빈님의 생각도 궁금하여 여쭤보았습니다!

긴 글 읽어주셔서 감사합니다 🙂

답변 2

0

박우빈님의 프로필 이미지
박우빈
지식공유자

안녕하세요, addvd 님!

 

1. createProduct는 대부분의 테스트 클래스에서 사용되는데 하나의 static 클래스와 같이 외부로 분리하는게 좋지 않을까 라는 생각을 해봤습니다.

강의 중에도 말씀드리겠지만, 물론 그렇게도 할 수 있는데요. test fixture 생성 로직을 통합하여 관리할 경우, 프로젝트의 복잡도가 높아짐에 따라 또다른 관리 대상이 될 수 있습니다. (조금 다르게 kotlin에서는, 메서드에 파라미터명을 지정(named arguments)할 수 있기 때문에 보다 깔끔한 공통 fixture 클래스를 만들 수 있긴 합니다.)

 

2. 저는 Order 클래스의 상태 변경을 위해서 updateOrderStatus라는 메소드를 생성 하였는데 (아직 해당 강의를 끝까지는 보지 않고 강의를 보기전에 먼저 테스트 코드를 작성 해보고 있습니다) 이런 식으로 테스트를 위해서 메소드를 생성 하는 경우도 괜찮을까요?

요것도 뒤쪽 강의에서 말씀 드리겠지만, 테스트를 위한 메서드는 최대한 지양해야 합니다.
그래서 Order 테스트의 경우 Order.create()라는 메서드를 사용하는 대신, 직접 status를 지정할 수 있는 Builder를 사용하여 Order를 생성하는 것이 좋습니다.

 

3. 제가 작성한 코드의 given 절이 너무 길어지는 느낌을 받았습니다. 현재는 order와 product를 생성 하는 정도이지만 더 복잡한 로직을 생성 할 경우 given이 너무 길어 지는것에 대한 해결 방안이 있을까요?

일단 상위 레이어의 테스트일수록, 복잡도가 높을 수록 given절이 길어지는 것은 자연스럽습니다. 상황에 맞게 메서드를 분리하고, 테스트 케이스에 간접적인 영향을 미치는 준비 과정은 BeforeEach를 사용하는 등 여러 방법을 사용해볼 수 있겠습니다. (이것도 뒤쪽 강의에 말씀드려요.)

도움이 되셨기를 바랍니다.
감사합니다. 🙂

 

0

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

addvd님께서 직접 작성하신 테스트 코드에 대한 몇 가지 고민에 대해 답변드리겠습니다.

  1. createProduct 분리: 많은 테스트 클래스에서 동일한 createProduct 메소드가 사용된다면 이를 별도의 유틸리티 클래스로 분리하여 static 메소드로 만드는 것은 좋은 선택일 수 있습니다. 이를 통해 코드의 중복을 줄이고 유지 보수를 용이하게 하며, 재사용성을 높일 수 있습니다.

  2. 테스트를 위한 메소드 생성: 테스트를 위해 updateOrderStatus와 같은 메소드를 추가하는 것은 일반적으로 괜찮습니다. 그러나 실제 서비스에 필요하지 않은 메소드라면, 테스트 클래스 내부에 있는 방식으로 접근하거나 별도의 테스트 전용 구성요소를 사용하는 것도 고려할 수 있습니다. 중요한 것은 테스트 코드가 실제 코드에 불필요한 복잡성을 추가하지 않도록 하는 것입니다.

  3. 긴 given 절 단축: given 절이 길어지는 문제는 종종 테스트 코드에서 발생할 수 있는 어려움 중 하나입니다. 이를 해결하기 위해서는 빌더 패턴을 사용하여 객체 생성을 더 간단하고 읽기 쉽게 만들거나, 공통적으로 사용되는 설정을 메소드로 추출하여 간결한 형태로 유지하는 방법이 있습니다. 또한, 복잡한 설정을 하는 부분이 있다면 그 설정 자체를 별도 메소드로 분리하여 given 절에서 호출하는 방식으로 명확성을 높일 수 있습니다.

addvd님께서 작성하신 코드에 대한 분석을 바탕으로 이러한 조언을 드립니다. 더 구체적인 솔루션을 원하시면 다른 전문가 의견도 참고해 보시는 것이 좋겠습니다.

감사합니다! 😊

Spring Boot Reference: Mocking and Spying Beans

addvd님의 프로필 이미지
addvd

작성한 질문수

질문하기