[TIL 49일 차] Spring TDD
Spring TDD
5. Spring Service 계층 테스트
5-01. Service 계층 테스트의 목적
비즈니스 로직의 정확성, 트랜잭션 일관성, 예외 처리 규약 준수, 외부 시스템과의 상호작용 계약을 점검
1) 테스트 대상 선정
- 테스트해야 할 것
- 비즈니스 로직이 포함된 메서드
- 트랜잭션이 필요한 메서드
- 테스트하지 말아야 할 것
- 단순 위임 메서드
- DTO 변환 메서드
2) 테스트 방법 개요
- 단위 테스트
- Mockito를 활용한 의존성 대체
- 비즈니스 로직 검증
- 통합 테스트
- 실제 의존성 활용 (DB, 실제 JPA, 외부 API Stub 서버 등)
- 트랜잭션 롤백 테스트
3) 테스트 4대 목적
- 비즈니스 로직의 정확성 검증
- 요구사항을 정확히 구현했는지 확인
- JUnit5 + Fake 저장소 사용
- 트랜잭션 동작 보장
- 성공 시 커밋, 실패 시 롤백
@Transactional사용
- 예외 처리 검증
- 명세된 예외 타입/메시지/코드 준수
- 외부 시스템 연동 검증
- 계약(요청/응답 스키마, 호출 횟수/순서, 타임아웃/재시도 정책)을 검증
5-02. 테스트 대상
1) 테스트해야 할 것
- 비즈니스 로직이 포함된 메서드
- 트랜잭션이 필요한 메서드
- 외부 연동 게약(순서/횟수/파라미터)
- 예외 정책(타입/코드/메시지)
2) 테스트하지 말아야 할 것
- 단순 위임 메서드 (로직 없이 다른 메서드만 호출)
- DTO 변환 메서드 (단순 매핑, getter/setter)
5-03. Service 계층 테스트 방법
- 단위 테스트(Unit Test)
- 목적 : 비즈니스 규칙의 정확성, 분기, 계산 검증 (빠른 피드백)
- 의존성을 Mockito로 대체하여 Service 로직만 격
- 통합 테스트(Integration Test)
- 목적 : 실제 의존성과 연결, 트랜잭션, 영속성 동작 검증
- 인메모리/로컬 리소스(H2, 파일, 인메모리 메시지 브로커 등)를 사용하여 실제처럼 동작하는 전체 흐름을 구성
6. Spring Controller 테스트
6-01. API 계층 테스트의 목적
Spring Web 계층은 HTTP 요청 ➡️ 파싱/검증 ➡️ Service 호출 ➡️ 응답을 담당한다. Controller 테스트는 서버를 띄우지 않고(ex: MockMvc) 이 흐름이 명세대로 동작하는지 검증
1) 요청 처리의 정확성 검증
Controller가 경로/메서드/파라미터를 올바르게 해석하고 Service로 정확히 위임하는지 검증
2) 입력값 검증
Bean Validation으로 필수/형식/길이 규칙을 선언하고, 검증 실패 시 400+오류 메시지를 보장
3) 응답 상태와 형식 검증
성공/실패 상태 코드와 JSON 스키마(필드명/자료형/페이징 메타)가 명세와 동일한지 확인
4) 예외 처리 검증
글로벌 예외 처리기가 도메인/검증 예외를 일관된 에러 형식으로 변환하는지 확인
6-02. API 계층의 테스트 대상
1) 테스트해야 할 것
- 요청 본문 검증이 필요한 엔드포인트
- Bean Validation (유효성 검증) 로직
- 커스텀 예외 처리가 있는 엔드포인트
@ControllerAdvice와@ExceptionHandler로 정의한 오류 처리’
2) 테스트하지 말아야 할 것
- 단순 Service 호출 위임 메서드
- 단순 DTO 변환 메서
3) @SpringBootTest를 이용한 API 계층 테스트 기본 구조
- 슬라이스 테스트
- MVC 계층만 빠르게 검증
@WebMvcTest(Controller_클래스.class)사용
- 통합형 테스트
- 전체 애플리케이션 컨텍스트를 함께 올려 검증
@SpringBootTest사용
@WebMvcTest(Controller_클래스.class)
@AutoConfigureMockMvc // MockMvc 자동 구성(Http Request를 위해 필요한 것들만 불러옴)
public class ControllerTestDefaultStructure {
@Autowired
private MockMvc mockMvc; // HTTP 요청 시뮬레이션 도구
@Test
void exampleTest() throws Exception {
// given: 요청 바디, 파라미터 준비
// when: mockMvc.perform()로 요청 전송
// then: 상태코드, 응답 본문, 헤더 검증
}
}
6-03. API 계층 테스트 코드 작성 방법
1) @WebMvcTest
Spring Boot 테스트 환경에서 Spring MVC 관련 컴포넌트만 로딩하는 애너테이션
즉, Controller, @ControllerAdvice, JSON 변환기(ObjectMapper), Validator 등 웹 계층 동작에 필요한 Bean만 초기화
@WebMvcTest(controllers = ...)는 지정한 Controller 클래스만 로딩- MockMvc를 사용하면 톰캣 서버 없이 HTTP 요청/응답을 시뮬레이션 가능
- Service나 Repository 계층은 로딩되지 않기 때문에, 필요한 경우
@MockitoBean을 사용하여 해당 Bean을 Mocking해야 한다. - 장점
- 테스트 속도가 빠름
- 웹 계층 계약(Contract)에만 집중 가능
2) @WebMvcTest를 이용한 API 계층 슬라이스 테스트 기본 구조
// test/java/com/example/api/ArticleControllerWebMvcTest.java
@WebMvcTest(controllers = ArticleController.class) // Controller 슬라이스 로딩
@Import(GlobalExceptionHandler.class) // 전역 예외 처리기 등록
class ArticleControllerWebMvcTest {
@Autowired private MockMvc mockMvc; // HTTP 요청 시뮬레이터
@Autowired private ObjectMapper om; // JSON 직렬화/역직렬화 도구
@MockitoBean private ArticleService articleService; // Service 계층 Mock 처리
}
3) POST 요청
요청 본문 검증과 응답 상태/헤더 확인
- 요청 본문 유효할 경우 ➡️ 201 Created + Location 헤더 + JSON 응답
- 요청 본문이 유효하지 않을 경우 ➡️ 400 Bad Request + 에러 코드/메시지
4) GET 요청 테스트
리소스 조회와 예외 처리 검증
- 존재하는 ID 요청 시 ➡️ 200 OK + JSON 본문
- 존재하지 않는 ID 요청 시 ➡️ 404 Not Found + 에러 응답(JSON 형식)
5) Spring Security 통합 테스트
보안이 적용된 Controller는 인증/권한이 테스트에 반영되어야 한다.
- Spring Security는 테스트 시 두 가지 접근 방식을 제공
@WithMockUser: 간단한 인증 주체를 MockingSecurityMockMvcRequestPostProcessors:user(),jwt()등으로 세밀한 인증 설정
7. 통합 테스트
7-01. 통합 테스트에서 @SpringBootTest 활용
통합 테스트(Integration Test)는 실제 Service 환경과 최대한 유사하게 애플리케이션을 기동하여 계층 간 연동과 환경 설정이 올바른지 검증하는 테스트
@SpringBootTest는 Spring Boot의 전체 ApplicationContext를 로딩하여, Service/Repository/AOP/이벤트/환경설정까지 모두 포함한 테스트 환경을 제공
- Bean 의존성 주입이 정상적으로 이루어지는지 검증 가능
- Web/Service/Repository 계층의 연동이 의도대로 동작하는지 검증 가능
- 실제 설정 파일(
application.yml)과의 결합이 잘 되는지 검증 가능
1) 테스트 프로파일 설정
테스트 환경은 운영 환경과 분리되어야 하며, DB 연결, API 주소, 로깅 레벨 등 모든 설정이 달라질 수 있다.
application-test.yml을 작성하고, @ActiveProfiles로 선택하면 테스트 실행 시 해당 설정이 적용
7-02. 테스트 데이터 관리
1) 데이터 초기화 전략
- 트랜잭션 롤백 기반
@Sql/data.sql스크립트- 마이그레이션 도구 활용
2) 테스트 데이터 작성 방식
- Builder/Fixture
3) 격리된 테스트 환경 구성
- 롤백 : 가장 간단하고 빠른 데이터 격리 방식
- TRUNCATE : 멀티 트랜잭션 환경이나 외부 시스템 연동 시 유용한 정리 방식
@Testcontainers: 실제 DB와 유사한 독립적인 테스트 환경을 구성하는 방식- 시간/랜덤 값 제어: 테스트 결과의 재현 가능성을 높이기 위한 방식
7-03. End-to-End 테스트
실제 사용자가 겪는 단일 시나리오를 시스템 경계를 넘어 끝까지 검증하는 테스트
- 단위/통합 테스트 : 구현 디테일과 계층 연동을 확인
- E2E : 계약과 구성의 총체적 적합성을 확인
1) 필요성
- 장점
- 기능의 완전성을 검증
- 사용자 관점 흐름을 확인
- 한계
- 느리고, 때때로 불안정
- 실패 시 원인 파악 비용이 큼
- 결론
- 핵심 사용자 흐름 위주로 적게, 정확히 운영
- 나머지 대부분은 단위/슬라이스/통합 테스트에서 커버
2) 실제 요청-응답 시나리오
- 테스트 목적
- Controller ➡️ Service ➡️ Repository까지 HTTP 레벨에서 실제 실행 경로를 검증
- 테스트 범위
- Tomcat 내장 서버를 띄우고 실제 포트로 요청을 보내며, 결과로 내려오는 상태 코드, 헤더, 본문(JSON)을 계약으로 고정
- 테스트 가치
- Spring MVC 바인딩, Jackson 직렬화/역직렬화, 트랜잭션 경계, JPA 플러시 타이밍 문제를 조기에 발견
- 환경과 도구
- 데이터베이스: H2 메모리 DB
- HTTP 클라이언트 : TestRestTemplate 또는 RestAssured
- 서버 모드 :
@SpringBootTest(webEnvironment = RANDOM_PORT) - 로깅 : 테스트 프로파일에서 로그만
debug, 나머지는warn
- 시나리오 설계 원칙
Given-When-Then패턴을 명시적으로 분리- 성공 1 + 실패 N
- API 문서를 계약으로 간주하고 테스트가 그 계약을 강제
3) 외부 시스템 통합
외부 호출을 감싸는 인터페이스를 만들고, 운영에서는 실제 구현된 모듈을, 테스트에서는 임시로 구현된 모듈(테스트 전용 가짜 구현 @Profile("test"))을 사용한다. 이렇게 하면 WireMock 같은 도구 없이 외부 의존 통제 가능
4) 전체 기능 검증
여러 엔트포인트를 사용자가 경험할 수 있는 흐름으로 엮어 한 방에 검증
8. 테스트 주도 개발(TDD)
8-01. TDD (Test-Driven Development)
테스트 주도 개발 방식으로, 개발 전에 먼저 테스트를 작성한 후 기능을 구현하는 개발 방법
- 테스트 작성 ➡️ 기능 구현 ➡️ 리팩터링
1) TDD 주기
- Red : 실패하는 테스트 먼저 작성
- Green : 테스트를 통과할 수 있을 정도로 최소한의 기능 구현
- Refactor : 테스트가 통과한 코드를 리팩터링
2) TDD의 장단점
- 장점
- 요구사항 분석이 선행되어 명확한 개발 가능
- 테스트 커버리지가 높아져 안정적인 코드 제공
- 리팩터링이 안전하게 가능
- 디버깅 시간 단축
- 분할 정복에 적합한 구조 유도
- 결합 발견 시점이 빨라짐
- 단점
- 초기 개발 속도가 느릴 수 있음
- 모든 상황에 TDD가 적합하지 않음
- 경험 부족 시 테스트 품질 저하
- 테스트 코드 유지에 리소스 소요
3) TDD vs BDD
| 구분 | TDD | BDD |
|---|---|---|
| 중심 | 개발자 중심의 단위 기능 테스트 | 사용자 중심의 시나리오 테스트 |
| 문법 | JUnit, Assert 등 | Given-When-Then 기반 (Cucumber, RestAssured 등) |
| 적합 | 내부 로직 검증, 유틸성 기능 테스트에 적합 | 사용자 시나리오, 비즈니스 로직 테스트에 적합 |
Leave a comment