
Controller에 로직이 몰려 있을 땐 테스트를 아예 안 하게 되거나, Mock을 과하게 쓰게 된다.
구조를 바꾼 뒤 테스트 작성은 더 쉬워졌고, 테스트 자체의 품질도 높아졌다.
@PostMapping("/orders")
public ResponseEntity<OrderResponse> createOrder(@RequestBody OrderRequest request) {
// 검증, DB 접근, 외부 API 호출, 로직 포함
...
return ResponseEntity.ok(...);
}
Controller → UseCase → (DomainService, Component)
| 계층 | 테스트 대상 | Mock 대상 | 테스트 전략 |
|---|---|---|---|
| Controller | 요청/응답 흐름 | UseCase | WebMvcTest + MockBean |
| UseCase | 로직 흐름, 트랜잭션 | Domain, 외부 API | 순수 단위 테스트 |
| DomainService | 계산, 정책 | 없음 | 순수 단위 테스트 |
| Component | 외부 API 통신 | 실제 API 또는 Stub | 통합 테스트 (TestContainers 등) |

@WebMvcTest(OrderController.class)
class OrderControllerTest {
...
}
class OrderCreateUseCaseTest {
...
}
class PricingServiceTest {
...
}
| 항목 | 기존 구조 | 분리 후 구조 |
|---|---|---|
| 테스트 난이도 | 높음 (통합 모킹 필요) | 낮음 (단일 책임 테스트 가능) |
| 실패 원인 파악 | 어려움 (모듈 간 책임 혼재) | 쉬움 (계층별 명확) |
| 유지보수 | 테스트 코드도 자주 변경 | 변경 영향도 낮음 |
| 테스트 커버리지 | 낮음 (테스트 회피 경향) | 높음 (단위 테스트 용이) |
| 항목 | 리팩토링 전 | 리팩토링 후 |
|---|---|---|
| 평균 테스트 코드 라인 수 | 100줄 이상 | 30~50줄 |
| Mock 객체 수 | 5개 이상 | 2개 내외 |
| 테스트 실행 시간 | 평균 3~5초 | 평균 1초 이하 |
| 실패 시 디버깅 시간 | 10분 이상 | 2~3분 내외 |
WebMvcTest, 나머지는 JUnit + MockitoStub 또는 TestContainer 활용Controller 책임을 분리했을 때, 테스트 전략도 함께 바뀌어야 효과가 극대화된다.
UseCase 중심의 단위 테스트는 빠르고 정확하며, 실패 원인 추적도 훨씬 명확하다.
실무에서 통하는 구조는 결국 테스트가 가능한 구조다.
오늘도 성장하는 개발자 되기! ㅍㅇㅌ!