일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 1차원 DP
- 2차원 dp
- 99클럽
- @BeforeAll
- @BeforeEach
- @Builder
- @Entity
- @GeneratedValue
- @GenericGenerator
- @NoargsConstructor
- @Query
- @Table
- @Transactional
- Actions
- Amazon EFS
- amazon fsx
- Android Studio
- ANSI SQL
- ApplicationEvent
- assertThat
- async/await
- AVG
- AWS
- Azure
- bind
- builder
- button
- c++
- c++ builder
- c03
- Today
- Total
기록
[JpaTest] Spring Layered Architecture와 Layer 테스트 전략 본문
시작하면서
Spring 애플리케이션에서 각 레이어는 명확한 책임 분리를 통해 유지보수성과 테스트 가능성을 향상시키며, 각 레이어에 적합한 테스트 전략을 적용할 수 있습니다. 예를 들어, Controller Layer는 클라이언트 요청을 처리하고 Service Layer를 호출하며, Service Layer는 비즈니스 로직을 처리하고 Repository Layer는 데이터 접근을 담당합니다. 아래는 Controller, Service, Repository 레이어 각각의 테스트 방법과 그 전략을 설명합니다.
1. Controller Layer
역할:
- 클라이언트의 요청(Request)을 수신하고, 적절한 Service를 호출하여 응답(Response)을 생성합니다.
- 입력값 검증과 같은 최소한의 논리만 처리합니다.
테스트 전략:
- Mock 객체 사용: Controller는 비즈니스 로직을 직접 포함하지 않으므로, Service를 Mocking하여 독립적으로 테스트합니다. 이는 Controller Layer의 테스트가 Service Layer의 로직에 의존하지 않고, 입력 검증 및 올바른 Service 호출 여부에 집중할 수 있도록 합니다.
- 테스트 방법:
- Spring MVC 테스트를 활용하여 요청과 응답의 동작을 검증합니다.
- 요청 파라미터 검증 및 적절한 HTTP 응답 코드 반환을 확인합니다.
예제 코드:
@DisplayName("신규 상품을 등록한다.")
@Test
void createProduct() throws Exception {
// given
ProductCreateRequest request = ProductCreateRequest.builder()
.type(ProductType.HANDMADE)
.sellingStatus(ProductSellingStatus.SELLING)
.name("아메리카노")
.price(4000)
.build();
// when // then
mockMvc.perform(
post("/api/v1/products/new")
.content(objectMapper.writeValueAsString(request))
.contentType(MediaType.APPLICATION_JSON)
)
.andDo(print())
.andExpect(status().isOk());
}
2. Service Layer
역할:
- 비즈니스 로직을 구현하며, 데이터 접근을 위해 Repository를 호출합니다.
- 트랜잭션 관리를 담당합니다.
테스트 전략:
- 실제 객체 사용: Mock 객체 대신 실제 Repository를 주입받아 비즈니스 로직을 검증합니다.
- 테스트 방법:
- JUnit과 Spring Context를 이용하여 Service 계층의 동작을 통합적으로 검증합니다. Spring Context를 사용하면 애플리케이션의 실제 동작 환경을 모방하여 서비스 계층의 로직과 데이터 접근 계층 간의 상호작용을 테스트할 수 있습니다. 예를 들어, 트랜잭션 롤백 동작이나 실제 데이터베이스 상호작용을 포함한 테스트를 수행할 수 있습니다.
- 다양한 입력값과 경계값에 대한 비즈니스 로직 처리를 테스트합니다.
예제 코드:
@Autowired
private ProductService productService;
@Autowired
private ProductRepository productRepository;
@DisplayName("신규상품을 등록할때, 상품번호는 가장 최근 상품의 상품번호에서 1 증가한 값으로 사용한다.")
@Test
void createProduct() {
// given
Product product1 = createProduct("001", HANDMADE, SELLING, "아메리카노", 4000);
productRepository.saveAll(List.of(product1));
ProductCreateServiceRequest createRequest = ProductCreateServiceRequest.builder()
.type(HANDMADE)
.sellingStatus(SELLING)
.name("카푸치노")
.price(5000)
.build();
// when
ProductResponse response = productService.createProduct(createRequest);
// then
assertThat(response)
.extracting("productNumber", "type", "sellingStatus", "name", "price")
.contains("002", HANDMADE, SELLING, "카푸치노", 5000);
}
3. Repository Layer
역할:
- 데이터 접근 및 저장을 담당하며, CRUD 연산을 처리합니다.
- 비즈니스 로직은 포함되지 않습니다.
테스트 전략:
- 실제 객체 사용: Repository 계층은 데이터베이스와의 상호작용을 검증하기 위해 실제 데이터베이스를 사용합니다.
- 테스트 방법:
- Spring Data JPA의 @SpringBotTest를 활용하여 Repository의 동작을 테스트합니다.
- 쿼리 메서드의 정확성과 데이터 일관성을 검증합니다.
예제 코드:
@Autowired
private ProductRepository productRepository;
@DisplayName("판매상태로 상품을 조회한다.")
@Test
void findAllBySellingStatusIn() {
// given
Product product1 = Product.builder()
.productNumber("001")
.type(HANDMADE)
.sellingStatus(SELLING)
.name("아메리카노")
.price(4000)
.build();
Product product2 = Product.builder()
.productNumber("002")
.type(HANDMADE)
.sellingStatus(HOLD)
.name("카페라떼")
.price(4500)
.build();
productRepository.saveAll(List.of(product1, product2));
// when
List<Product> products = productRepository.findAllBySellingStatusIn(List.of(SELLING, HOLD));
// then
assertThat(products).hasSize(2)
.extracting("productNumber", "name", "sellingStatus")
.containsExactly(
tuple("001", "아메리카노", SELLING),
tuple("002", "카페라떼", HOLD)
);
}
추가 배경: 테스트 기준에 대한 논의
사이드 프로젝트 중, "서비스 테스트에서 왜 Mock만 사용하냐? 실제 객체를 사용하는 기준은 무엇이냐?"라는 질문을 팀원에게 받은 경험이 있습니다. 이 질문을 통해 각 계층에서 Mock과 실제 객체의 사용 기준을 재검토하게 되었고, 이를 기반으로 테스트 전략을 구체화할 수 있었습니다. 이러한 기준은 각 레이어의 책임을 분리하고 테스트 목적에 맞는 적합한 방법을 선택하는 데 도움을 주었습니다. 이 질문을 계기로 테스트 방법에 대한 강의를 들었고, 해당 강의에서는 다음과 같은 기준으로 Mock과 실제 객체 사용을 구분하였습니다:
- Mock 사용:
- 외부 의존성이 있는 경우, 해당 의존성을 Mock으로 대체하여 독립적으로 테스트합니다.
- 특정 계층의 기능만 검증할 때 사용됩니다 (예: Controller Layer).
- 실제 객체 사용:
- 비즈니스 로직의 복잡성을 검증하거나, 데이터베이스와의 상호작용을 포함한 통합 테스트를 수행할 때 사용합니다.
- Repository 및 Service 계층에서는 실제 객체를 활용하여 더 깊이 있는 검증이 가능합니다.
이 기준을 통해, 각 계층에서의 테스트 전략을 더 명확히 구분하고, 필요한 수준의 검증을 적절히 수행할 수 있었습니다.
결론
위의 테스트 전략을 통해 각 레이어에 적합한 검증을 수행함으로써, 애플리케이션의 신뢰성을 높이고 유지보수성을 향상시킬 수 있습니다. Controller는 Mock 객체로 경량화된 테스트를 수행하고, Service와 Repository는 실제 객체를 사용하여 더욱 깊이 있는 검증을 진행합니다. 또한, Mock과 실제 객체의 사용 기준을 명확히 함으로써 테스트 설계의 명확성을 높였습니다.
'교육 > 강의' 카테고리의 다른 글
[JpaTest] CQRS와 서비스 레이어에서의 트랜잭션 관리 (0) | 2025.02.05 |
---|---|
[JpaTest] Spring Bean Validation 활용 가이드 (0) | 2025.02.04 |
[JpaTest] Layered Architecture vs Hexagonal Architecture (0) | 2025.01.26 |
[TDD] Test Driven Development, Red-Green-Refactor (0) | 2025.01.23 |
[단위테스트] 테스트하기 어려운 영역 분리: 현재 시간 의존성 해결 (0) | 2025.01.22 |