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

kamser님의 프로필 이미지

작성한 질문수

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

Business Layer 테스트 (2)

Persistence 계층 단위테스트를 어디까지 하는게 맞을까요?

해결된 질문

23.05.13 12:39 작성

·

787

·

수정됨

1

 @DisplayName("상품 번호 리스트로 상품목록을 조회하기")
 @Test
 void findAllByProductNumberIn(){
     //given
     Product product1 = createProduct("001", 1000);
     Product product2 = createProduct("002", 3000);
     Product product3 = createProduct("003", 5000);
     productRepository.saveAll(List.of(product1, product2, product3));

     //when
     List<Product> findProducts = productRepository.findAllByProductNumberIn(List.of("001", "003"));

     //then
     assertThat(findProducts).hasSize(2)
             .extracting("productNumber","price")
             .containsExactlyInAnyOrder(
                    tuple("001",1000),
                    tuple("003",5000)
             );
 }

1.

데이터 계층은 유효한 상품 번호 리스트만 넘어왔다는 전제로만 테스트를 해도 충분한가요?

유효하지 않은 상품번호는 검증 로직에서 걸러졌다고 생각하고

테스트를 한다고 생각하면 될까요?

  @DisplayName("미등록된 상품 번호 리스트로 상품목록을 조회하기")
    @Test
    void findAllByProductNumberIn(){
        //given
        Product product1 = createProduct("001", 1000);
        Product product2 = createProduct("002", 3000);
        Product product3 = createProduct("003", 5000);
        productRepository.saveAll(List.of(product1, product2, product3));

        //when
        List<Product> findProducts = productRepository.findAllByProductNumberIn(List.of("004", "005"));

        //then
        assertThat(findProducts).hasSize(0);
    }

 

2.

JpaRepository가 제공하는 기본 save,findAll 등도

개발자가 원하는 데이터가 올바르게 저장 되었는지, 조회가 되었는지 테스트도

실무에서는 작성하시나요 ?

아니면 이미 만들어진 코드이기 때문에 불필요한 테스트 코드라 생각해서

넘어가는지 궁금합니다.

 


    @DisplayName("주문 생성시 상품 리스트에서 주문의 총 금액을 계산한다.")
    @Test
    void calculrateTotalPrice(){
        //given
        List<Product> products = List.of(
                createProduct("001", 1000),
                createProduct("002", 2000)
        );
        //when
        Order order = Order.create(products, LocalDateTime.now());

        //then
        assertThat(order.getTotalPrice()).isEqualTo(3000);
    }

    private Product createProduct(String productNumber, int price) {
        
        return Product.builder()
                .type(HANDMADE)
                .productNumber(productNumber)
                .price(price)
                .sellingStatus(SELLING)
                .name("메뉴 이름")
                .build();
    }

    @DisplayName("주문 생성시 주문 상태는 INIT 이다.")
    @Test
    void init(){
        //given
        List<Product> products = List.of(
                createProduct("001", 1000),
                createProduct("002", 2000)
        );
        //when
        Order order = Order.create(products, LocalDateTime.now());

        //then
        assertThat(order.getOrderStatus()).isEqualByComparingTo(OrderStatus.INIT);
    }

    @DisplayName("주문 생성시 등록시간을 기록한다.")
    @Test
    void registeredDataTime(){
        //given
        LocalDateTime registeredDateTime = LocalDateTime.now();
        List<Product> products = List.of(
                createProduct("001", 1000),
                createProduct("002", 2000)
        );
        //when
       
        Order order = Order.create(products, registeredDateTime);

        //then
        assertThat(order.getRegisteredDateTime()).isEqualTo(registeredDateTime);
    }

Order.create 테스트 코드를 작성할때엔

각각 필드 초기화를 단위테스트를 진행했는데,

    @DisplayName("주문번호 리스트를 받아 주문을 생성한다.")
    @Test
    void createOrder(){
        
        LocalDateTime registeredDateTime = LocalDateTime.now();
        
        Product product1 = createProduct(HANDMADE, "001", 1000);
        Product product2 = createProduct(HANDMADE, "002", 3000);
        Product product3 = createProduct(HANDMADE, "003", 5000);
        
        productRepository.saveAll(List.of(product1, product2, product3));
        
        OrderCreateRequest request = OrderCreateRequest.builder()
                .productNumbers(List.of("001", "002"))
                .build();

        OrderResponse orderResponse = orderService.createOrder(request, registeredDateTime);
        assertThat(orderResponse.getId()).isNotNull();
        assertThat(orderResponse)
                .extracting("registeredDateTime","totalPrice")
                .contains(registeredDateTime,4000);
        assertThat(orderResponse.getProducts()).hasSize(2)
                .extracting("productNumber","price")
                .containsExactlyInAnyOrder(
                    tuple("001",1000),
                    tuple("002",3000)
                );
    }

createOrder 테스트 코드는 같이 검사를 했습니다.

3.

각 초기화를 해주는 정적 매서드를 테스트 코드로 작성을 했는데

Order.create 테스트 코드는 필드마다 분리를 해서 테스트 코드를 작성하고,

createOrder 테스트 코드는 같이 검사를 했습니다.

또 강의에는 없지만 ProductResponse.of 정적 메서드로도 초기화를 했는데

따로 분리해서 테스트 코드를 작성하지 않고

OrderServiceTest의 createOrder()에서 같이 테스트 코드에 포함되었습니다.

테스트 코드를 분리하는 경우와 같이 검사하는 경우를 나누는 기준이 있을까요?

답변 1

2

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

2023. 05. 15. 09:52

안녕하세요, kamser님! :)
하나씩 답변 드리겠습니다.

1.
데이터 계층은 유효한 상품 번호 리스트만 넘어왔다는 전제로만 테스트를 해도 충분한가요?
유효하지 않은 상품번호는 검증 로직에서 걸러졌다고 생각하고
테스트를 한다고 생각하면 될까요?

네, 맞습니다. 데이터에 엑세스한다는 행위는 그 이전에 어딘가에서 데이터를 저장할 때 유효한 데이터들을 필터링하거나 비즈니스 요구사항에 맞게 정제하여 저장했다는 것을 전제로 하죠.
데이터베이스에 유효하지 않은 값이 저장되어 있다면 이미 데이터 자체가 신뢰할 수 없는, 오염된 데이터셋이라고 봐야 할 테니까요 ㅎㅎ
테스트 코드를 작성할 때 염두에 두어야 하는 것 중 하나는, 우리가 모든 시나리오를 가정하고 테스트 코드를 작성할 수 있지만 언제나 허락된 시간과 비용 안에서 최대의 효율을 가져가면서 테스트 케이스를 선택해야 한다는 것입니다. :)

2.
JpaRepository가 제공하는 기본 save,findAll 등도
개발자가 원하는 데이터가 올바르게 저장 되었는지, 조회가 되었는지 테스트도
실무에서는 작성하시나요 ?
아니면 이미 만들어진 코드이기 때문에 불필요한 테스트 코드라 생각해서
넘어가는지 궁금합니다.

기본적으로 제공되는 기능에 대한 테스트는 작성하지 않아요 ㅎㅎ
최근 질문 중에 비슷한 답변이 있어 참고해 보시면 좋을 것 같아요 :)
https://www.inflearn.com/questions/867662

3.
각 초기화를 해주는 정적 매서드를 테스트 코드로 작성을 했는데
Order.create 테스트 코드는 필드마다 분리를 해서 테스트 코드를 작성하고,
createOrder 테스트 코드는 같이 검사를 했습니다.
또 강의에는 없지만 ProductResponse.of 정적 메서드로도 초기화를 했는데
따로 분리해서 테스트 코드를 작성하지 않고
OrderServiceTest의 createOrder()에서 같이 테스트 코드에 포함되었습니다.
테스트 코드를 분리하는 경우와 같이 검사하는 경우를 나누는 기준이 있을까요?

테스트 케이스 분리 기준에 관한 내용이라기 보다는, 단위 테스트와 통합 테스트의 차이에서 비롯된 코드라고도 볼 수 있겠는데요.
Order라는 객체 생성 로직에 대해 단위 테스트를 작성하면서, 저는 명확하게 검증해야 할 케이스가 여러 가지라고 본 것이고, 구체적으로는 DisplayName에 작성한 것처럼 3가지 형태의 비즈니스 로직이 존재한다고 보았기 때문에 분리해서 테스트 케이스를 작성했습니다.
반대로 서비스 레이어 통합 테스트에서는 서비스 레이어의 책임, 즉 트랜잭션 경계와 비즈니스 로직 전개를 테스트하는 것을 목표로 했기 때문에 모든 Order 필드에 대한 부분을 한 번에 검증할 수 있다고 생각해서 하나의 테스트로 작성했습니다.
(이미 단위 테스트로 Order 생성 로직에 대한 상세한 단위 테스트가 뒷받침되고 있었기 때문에 더 가능한 결정이었죠.)
케이스 분리에 정답은 없지만, 두 가지 경우에 대한 의사결정 방식, 테스트하고자 하는 목표가 달랐기 때문에 다른 형태로 코드가 작성되었다는 점을 염두에 두시면 좋을 것 같네요 :)

도움이 되셨기를 바라며, 좋은 질문 남겨주셔서 감사합니다. :)

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

2023. 05. 15. 11:07

강사님 강의 너무 잘 듣고 있습니다.

특히 키워드 정리를 통해서 정리해주실때 내가 강의를 보면서 어디까지 제대로 이해를 했는지

다시 확인할 수 있다는 장점과

강사님께서 주황색 깃발(?)로 스스로 찾아보라는 숙제를 주시는 것도 너무 도움이 됩니다.

처음에 강의를 결제할때 기대보다 걱정이 더 컷는데 너무 만족하고 있습니다.

감사합니다 (__)

kamser님의 프로필 이미지

작성한 질문수

질문하기