[워밍업 클럽 3기 BE 클린코드&테스트] - 레이어드 아키텍처 주요 특징
Spring에서 애플리케이션을 설계할 때 흔히 사용하는 Layered Architecture(레이어드 아키텍처)는 다음과 같은 3가지 주요 계층으로 구성
[Presentation Layer] → [Business Layer] → [Persistence Layer]
(Controller) (Service) (Repository)
Layered Architecture의 핵심 특징
계층 분리 : 각 계층이 독립된 책임을 가지며, 역할이 명확히 나뉨
독립성 : 상위 계층은 하위 계층을 의존하지만, 하위 계층은 상위 계층을 모름
유연성 : 특정 계층을 변경하더라도 다른 계층에 영향이 적음
테스트 용이성 : 계층별 단위 테스트가 가능해 테스트 작성이 명확하고 쉬움
1. Presentation Layer (UI 계층 - Controller)
✅ 역할
사용자 요청을 받아서 적절한 응답을 반환 (주로 JSON 형태)
HTTP 관련 처리 (
@RestController
,@GetMapping
등)
✅ 테스트 전략
@WebMvcTest
를 사용하여 컨트롤러만 테스트MockMvc + ObjectMapper로 HTTP 요청/응답 흐름을 검증
Service는 @MockBean으로 주입
📌 예시 코드
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)
✅ 역할
비즈니스 로직 처리의 중심
트랜잭션 처리, 여러 레포지토리 호출, 외부 API 조합 등
✅ 테스트 전략
@SpringBootTest
또는 순수 JUnit 테스트로 비즈니스 로직 검증Repository는 필요 시 가짜(Mock) 또는 실제 DB 사용
Happy Path + 예외 케이스 + 경계값 등 다양한 시나리오 테스트
📌 예시 코드
@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)
✅ 역할
데이터베이스와 직접 상호작용 (CRUD, 조건 검색 등)
JPA, MyBatis 등 ORM/쿼리 도구 사용
✅ 테스트 전략
@DataJpaTest
를 사용하여 가벼운 JPA 테스트테스트 종료 시 자동으로 Rollback 처리됨
실제 DB 조회/저장 동작 검증
📌 예시 코드
@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)
);
}
요약
Controller : HTTP 요청/응답 처리,
@WebMvcTest
+ MockMvc로 요청 파라미터, 상태 코드, JSON 응답 값 등을 테스트Service : 비즈니스 로직 처리,
@SpringBootTest
or JUnit + Mockito로 로직 분기, 예외 처리, 흐름 제어 등 테스트Repository : 데이터 CRUD 및 조회,
@DataJpaTest
로 쿼리 정확성, 연관 관계 매핑, 조건 검색 등을 테스트
댓글을 작성해보세요.