inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

[워밍업 클럽 3기 BE 클린코드&테스트] - 레이어드 아키텍처 주요 특징

5jeong
0

Spring에서 애플리케이션을 설계할 때 흔히 사용하는 Layered Architecture(레이어드 아키텍처)는 다음과 같은 3가지 주요 계층으로 구성

[Presentation Layer] → [Business Layer] → [Persistence Layer]
     (Controller)         (Service)           (Repository)

Layered Architecture의 핵심 특징


1. Presentation Layer (UI 계층 - Controller)

역할

테스트 전략

📌 예시 코드

WebMvcTest(controllers = OrderController.class)
class OrderControllerTest {

    @Autowired MockMvc mockMvc;
    @Autowired ObjectMapper objectMapper;
    @MockBean OrderService orderService;

    @Test
    void 신규_주문_등록_성공() throws Exception {
        OrderCreateRequest request = OrderCreateRequest.builder()
                .productNumbers(List.of("001"))
                .build();

        mockMvc.perform(post("/api/v1/orders/new")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value("200"))
                .andExpect(jsonPath("$.message").value("OK"));
    }

    @Test
    void 상품번호_없을때_예외() throws Exception {
        OrderCreateRequest request = OrderCreateRequest.builder().build();

        mockMvc.perform(post("/api/v1/orders/new")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.message").value("상품 번호 리스트는 필수입니다."));
    }
}

2. Business Layer (서비스 계층 - Service)

역할

테스트 전략

📌 예시 코드

@SpringBootTest
class ProductServiceTest {

    @Autowired
    private ProductService productService;

    @Autowired
    private ProductRepository productRepository;


    @AfterEach
    void tearDown() {
        productRepository.deleteAllInBatch();
        ;
    }

    @DisplayName("신규 상품을 등록한다. 상품번호는 가장 최근 상품의 상품번호에서 1증가한 것이다.")
    @Test
    void createProduct() {
        //given
        Product product = createProduct("001", HANDMADE, SELLING, "아메리카노", 4000);
        productRepository.save(product);
        ProductCreateServiceRequest request = ProductCreateServiceRequest.builder()
                .type((HANDMADE))
                .sellingStatus((SELLING))
                .name("카푸치노")
                .price(5000)
                .build();

        //when
        ProductResponse productResponse = productService.createProduct(request);

        //then
        assertThat(productResponse)
                .extracting("productNumber", "type", "sellingStatus", "name", "price")
                .contains("002", HANDMADE, SELLING, "카푸치노", 5000);

        List<Product> products = productRepository.findAll();

        assertThat(products).hasSize(2)
                .extracting("productNumber", "type", "sellingStatus", "name", "price")
                .containsExactlyInAnyOrder(
                        tuple("001", HANDMADE, SELLING, "아메리카노", 4000),
                        tuple("002", HANDMADE, SELLING, "카푸치노", 5000)
                );
    }

주의: 테스트에서 @Transactional을 사용할 경우, 실제 코드와 작동 방식이 달라질 수 있음 → 필요에 따라 deleteAllInBatch()로 정리


3. Persistence Layer (저장소 계층 - Repository)

역할

테스트 전략

📌 예시 코드

@ActiveProfiles("test")
//@SpringBootTest
@DataJpaTest
class ProductRepositoryTest {

    @Autowired
    private ProductRepository productRepository;

    @DisplayName("원하는 판매상태를 가진 상품들을 조회한다.")
    @Test
    void findAllBySellingStatusIn() {
        //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));

        //when
        List<Product> products = productRepository.findAllBySellingStatusIn(List.of(SELLING, HOLD));

        //then

        assertThat(products).hasSize(2)
                .extracting("productNumber", "name", "sellingStatus")
                .containsExactlyInAnyOrder(
                        tuple("001", "아메리카노", SELLING),
                        tuple("002", "카페라떼", HOLD)
                );
    }

요약

답변 0