[Test] Business Layer Test(with Spring Boot)

2025. 7. 5. 18:21·Software Development/Test

지난 글에서는 Persistence Layer를 테스트하며 기반을 다졌다. 이제 애플리케이션의 핵심, 비즈니스 로직을 구현하는 Business Layer(Service) 를 테스트해볼 차례다.

Business Layer (Service) 테스트

Service 계층은 Persistence Layer와 상호작용하며 비즈니스 로직을 전개하고, 트랜잭션을 보장해야 하는 아주 중요한 역할이다. 여기서는 하위 Repository 계층까지 아우르는 통합 테스트를 진행하며 TDD 방식으로 개발해보자.

요구사항: 상품 번호 리스트를 받아 주문을 생성한다.

TDD 사이클에 따라, 실패하는 테스트부터 작성한다.

// OrderServiceTest.java
@SpringBootTest
@ActiveProfiles("test")
class OrderServiceTest {

    @Autowired private OrderService orderService;
    @Autowired private ProductRepository productRepository;
    @Autowired private OrderRepository orderRepository;
    @Autowired private OrderProductRepository orderProductRepository;

    // 테스트가 서로 영향을 주지 않도록 데이터를 청소한다.
    @AfterEach
    void tearDown() {
        orderProductRepository.deleteAllInBatch();
        orderRepository.deleteAllInBatch();
        productRepository.deleteAllInBatch();
    }

    @DisplayName("상품번호 리스트를 받아 주문을 생성한다.")
    @Test
    void createOrder() {
        // given
        // 미리 상품들을 저장해둔다.
        Product product1 = createProduct(HANDMADE, "001", 1000);
        Product product2 = createProduct(HANDMADE, "002", 3000);
        productRepository.saveAll(List.of(product1, product2));

        OrderCreateRequest request = OrderCreateRequest.builder()
                .productNumbers(List.of("001", "002"))
                .build();

        // when
        OrderResponse orderResponse = orderService.createOrder(request, LocalDateTime.now());

        // then
        assertThat(orderResponse.getId()).isNotNull();
        assertThat(orderResponse.getTotalPrice()).isEqualTo(4000);
        assertThat(orderResponse.getProducts()).hasSize(2)
                .extracting("productNumber")
                .containsExactlyInAnyOrder("001", "002");
    }

    // ... createProduct() 헬퍼 메서드
}

이 테스트를 통과시키기 위해 Order 도메인 객체와 OrderService를 구현한다.

TDD 중 만난 문제와 해결 과정

시나리오 추가: 중복된 상품번호로 주문할 수 있어야 한다. (ex. 아메리카노 2잔)

새로운 테스트 케이스(createOrderWithDuplicateProductNumbers)를 추가하고 돌려보니, 실패했다. 기존 productRepository.findAllByProductNumberIn()은 IN ('001', '001') 쿼리를 날려 상품을 하나만 가져왔기 때문이다.

이 문제를 해결하기 위해 서비스 로직을 수정했다.

// OrderService.java
public OrderResponse createOrder(OrderCreateRequest request, LocalDateTime registeredDateTime) {
    List<String> productNumbers = request.getProductNumbers();
    // (수정) 상품 번호에 맞는 상품 엔티티를 찾아온다. 중복을 허용하기 위한 로직.
    List<Product> products = findProductsBy(productNumbers);

    // 재고 차감 로직 ...

    Order order = Order.create(products, registeredDateTime);
    Order savedOrder = orderRepository.save(order);
    return OrderResponse.of(savedOrder);
}

// 상품 번호 목록으로 Product 엔티티 맵을 만들고, 다시 상품 번호 목록으로 조회해 중복을 처리
private List<Product> findProductsBy(List<String> productNumbers) {
    List<Product> products = productRepository.findAllByProductNumberIn(productNumbers);
    Map<String, Product> productMap = products.stream()
            .collect(Collectors.toMap(Product::getProductNumber, p -> p));

    return productNumbers.stream()
            .map(productMap::get)
            .toList();
}

테스트의 가장 큰 함정: 데이터 격리

각각의 테스트는 성공하는데, 클래스의 모든 테스트를 한 번에 실행하니 실패하는 경우가 있다. 바로 테스트 간 데이터 격리가 되지 않았기 때문이다.

  • @DataJpaTest는 테스트마다 트랜잭션을 걸고 끝나면 롤백해주지만,
  • @SpringBootTest는 그런 기능이 없다.

따라서 @SpringBootTest를 사용할 때는 한 테스트가 만든 데이터가 다음 테스트에 영향을 주지 않도록 직접 데이터를 지워줘야 한다. 이것이 위 코드에 @AfterEach로 tearDown() 메서드를 만든 이유다.

주의! 그럼 테스트 클래스에 @Transactional을 붙이면 되지 않을까?
편리하긴 하지만 아주 위험한 발상이다. 만약 실제 OrderService에 @Transactional을 다는 걸 깜빡해도, 테스트 코드의 @Transactional 때문에 테스트는 성공해버린다. 결국 운영 환경에서 터질 폭탄을 만드는 셈이다. 이 사이드 이펙트를 정확히 이해하고 조심해서 사용해야 한다.

다음 편에서는 외부 세계의 요청을 받는 Presentation Layer를 Mocking을 통해 어떻게 '순수하게' 테스트하는지 알아보겠다.

Ref) Practical Testing: 실용적인 테스트 가이드 강의 | 박우빈 - 인프런

'Software Development > Test' 카테고리의 다른 글

[Test] Mock(with Mockito)  (0) 2025.07.05
[Test] Presentation Layer Test(with Spring Boot)  (0) 2025.07.05
[Test] Persistence Layer Test(with Spring Boot, JPA)  (0) 2025.07.05
[Test] 테스트는 '문서'다.  (0) 2025.07.05
[Test] TDD(Test Driven Development)  (0) 2025.07.05
'Software Development/Test' 카테고리의 다른 글
  • [Test] Mock(with Mockito)
  • [Test] Presentation Layer Test(with Spring Boot)
  • [Test] Persistence Layer Test(with Spring Boot, JPA)
  • [Test] 테스트는 '문서'다.
lumana
lumana
배움을 나누는 공간 https://github.com/bebeis
  • lumana
    Brute force Study
    lumana
  • 전체
    오늘
    어제
    • 분류 전체보기 (456)
      • Software Development (27)
        • Performance (0)
        • TroubleShooting (1)
        • Refactoring (0)
        • Test (8)
        • Code Style, Convetion (0)
        • DDD (0)
        • Software Engineering (18)
      • Java (71)
        • Basic (5)
        • Core (21)
        • Collection (7)
        • 멀티스레드&동시성 (13)
        • IO, Network (8)
        • Reflection, Annotation (3)
        • Modern Java(8~) (12)
        • JVM (2)
      • Spring (53)
        • Framework (12)
        • MVC (23)
        • Transaction (3)
        • AOP (11)
        • Boot (0)
        • AI (0)
      • DB Access (1)
        • Jdbc (1)
        • JdbcTemplate (0)
        • JPA (14)
        • Spring Data JPA (0)
        • QueryDSL (0)
      • Computer Science (129)
        • Data Structure (27)
        • OS (14)
        • Database (10)
        • Network (21)
        • 컴퓨터구조 (5)
        • 시스템 프로그래밍 (23)
        • Algorithm (29)
      • HTTP (8)
      • Infra (1)
        • Docker (1)
      • 프로그래밍언어론 (15)
      • Programming Language(Sub) (77)
        • Kotlin (1)
        • Python (25)
        • C++ (51)
        • JavaScript (0)
      • FE (11)
        • HTML (1)
        • CSS (9)
        • React (0)
        • Application (1)
      • Unix_Linux (0)
        • Common (0)
      • PS (13)
        • BOJ (7)
        • Tip (3)
        • 프로그래머스 (0)
        • CodeForce (0)
      • Book Review (4)
        • Clean Code (4)
      • Math (3)
        • Linear Algebra (3)
      • AI (7)
        • DL (0)
        • ML (0)
        • DA (0)
        • Concepts (7)
      • 프리코스 (4)
      • Project Review (6)
      • LegacyPosts (11)
      • 모니터 (0)
      • Diary (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
lumana
[Test] Business Layer Test(with Spring Boot)
상단으로

티스토리툴바