> For the complete documentation index, see [llms.txt](https://leehyokyun.gitbook.io/openbook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://leehyokyun.gitbook.io/openbook/2-10.-tdd.md).

# 2-10. TDD

TDD에 대하여

### 1. 테스트 더블(Test Double)의 개념적 유래와 기법별 매커니즘 분리

* 테스트 더블의 유래: 영화 산업에서 위험한 스턴트 장면을 촬영할 때 배우 대신 투입되는 '대역 배우(Stunt Double)'에서 유래되었다. 실제 운영 객체를 테스트 런타임에 직접 투입하기 어렵거나 위험할 때, 대신 테스트 로직을 수행할 수 있는 대역 객체를 투입하여 검증이 가능하도록 지원하는 핵심 도구다.
* Mock 객체화 단계: 검증하고자 하는 서비스 및 특정 관심 대상을 격리하기 위해 해당 의존성을 Mock 객체화한다.
* 상태 보장(Stub) 단계: Mock 객체 선언 후, 동작 검증의 사전 작업으로서 특정 객체에 대한 상태를 보장하는 스텁(Stub) 작업을 선행해야 한다. `when(repo.findById(1L)).thenReturn(user);`와 같이 미리 정의된 반환값을 지정하는 것이 Stub의 역할이다. 만약 이를 정의하지 않는다면 Mockito 메커니즘에 의해 0, 1, null 등 시스템 기본값만 반환된다.
* 동작 검증(Mocking) 단계: `verify(repo).save(user);`와 같이 특정 동작이 실제로 실행되었는지를 추적하고 검증하는 단계다. 이때의 핵심 관심 여부는 반환값이 아닌, 대상 객체의 특정 행위(메서드 호출)가 발생했는지 여부다. BDDMockito 스타일에서는 `given()`을 통해 Stub을 정의하고, `then()`을 통해 Mocking 및 단언(Assert)을 수행한다.
* 테스터블(Testable)의 정의: 테스트 코드가 원활하게 작성될 수 있도록 비즈니스 서비스 및 내부 아키텍처 로직을 테스트 가능한 구조로 깨끗하게 분리하고 구성하는 개발 기법 자체를 의미한다.

### 2. 목적에 부합하는 테스트 케이스별 격리 환경 구성 전략

* 무조건적인 @SpringBootTest 사용 지양: 모든 테스트 케이스에 스프링 부트 컨텍스트 전체를 가동하는 `@SpringBootTest`를 무분별하게 선언하면 전체 테스트 구동 성능이 극심하게 저하된다. 따라서 테스트의 목적(단위 테스트, 레포지토리 테스트, E2E 테스트 등)에 맞게 정확한 범위를 한정하는 격리 전략이 필수적이다.
* POJO 기반 단위 테스트(Unit Test): 외부 프레임워크나 스프링 컨텍스트의 개입을 완전히 배제하고, 순수한 자바 객체(POJO)와 비즈니스 로직 자체만을 고립시켜 검증한다. 스프링 구동 오버헤드가 전혀 없으므로 밀리초(ms) 단위의 초고속 피드백 루프를 완성한다.
* 데이터 액세스 레이어 테스트(@DataJpaTest): 데이터베이스 연산에만 초점을 맞추어 JPA 및 레포지토리 빈 구조 등 필요한 인프라 컨텍스트만 부분적으로 가동하여 영속성 계층의 동작을 정밀하게 검증한다.
* 실제 인프라 통합 및 E2E 테스트: 실제 데이터베이스나 외부 메시지 큐(Redis, MySQL 등)에 대한 연동 및 호출 여부를 최종적으로 명확히 규명해야 할 때만 별도의 무거운 의존성 테스트 프로토콜을 가동한다.

### 3. Over Testing

* Over testing의 본질적 한계: 테스트 커버리지를 기계적으로 높이기 위해 모든 내부 지엽적인 메서드와 세부 구현까지 과도하게 테스트 케이스로 만드는 Over testing(과도한 테스트) 방식은 심각한 아키텍처 부작용을 낳는다.
* 높은 결합도와 리팩토링 저해 현상: 테스트 커버리지가 무의미하게 높아질수록, 테스트 코드가 비즈니스 로직의 세부 구현 사항과 고도로 결합(High Coupling)되는 현상이 발생한다. 이로 인해 실제 운영 코드의 내부 구조를 조금만 개선하거나 리팩토링을 단행하더라도, 그와 연결된 수많은 테스트 로직이 일제히 깨져 도미노처럼 무너진다.
* 유지보수 비용 폭증: 결과적으로 코드 변경점이 발생할 때마다 실제 프로덕션 비즈니스 로직을 수정하는 시간보다 관련된 테스트 코드를 일일이 분석하고 다시 재반영하는 유지보수 리소스와 비용이 훨씬 더 커지게 되므로, 테스트 대상의 명확한 경계 설정과 의도적인 범위 조율이 반드시 수반되어야 한다.

### 4. 테스트 데이터베이스 상태 전이 및 트랜잭션 롤백(@Transactional)의 한계점

* 격리성 유지를 위한 데이터 초기화: 각 테스트 케이스는 이전 테스트의 실행 결과에 영향을 받지 않고 완전히 격리된 상태에서 독립적으로 수행되어야 한다. 이를 위해 테스트 데이터베이스 상태를 매번 원점으로 되돌리는 작업이 수반된다.
* @Transactional을 통한 롤백 메커니즘의 편의성: 테스트 메서드 상단에 `@Transactional`을 마킹하면, 테스트 내에서 수행된 모든 INSERT, UPDATE, DELETE 쿼리는 실제 DB에 영구 반영되지 않고 테스트 종료와 동시에 커밋 없이 일괄 롤백(Rollback)되어 초기 상태를 유지해 준다.
* 물리적 분리 환경에서의 롤백 한계: 그러나 WebTestClient를 활용한 실제 HTTP 호출 테스트나, `@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)`를 가동하여 별도의 실제 내장 톰캣(Tomcat) 스레드가 기동되는 인프라 환경에서는 이 자동 롤백 메커니즘이 완전히 무력화된다.
* 커넥션 분리로 인한 데이터 격리 파괴 원인: 스프링 테스트 컨텍스트가 관리하는 트랜잭션 커넥션과, 내장 톰캣 서버가 가동되어 데이터베이스에 접근하는 커넥션은 물리적으로 서로 다른 별개의 스레드 및 커넥션 풀 자원이다. 따라서 톰캣 스레드 안에서 수행되어 물리적 커밋이 완료된 데이터는 테스트 메서드의 `@Transactional` 선언만으로는 절대 롤백할 수 없다. 이 경우 무조건 각 테스트 실행 직후(`@AfterEach`) 데이터베이스 테이블을 직접 비워주거나(Truncate), 테스트 실행 전에 스키마를 초기화하는 별도의 데이터 격리 프로토콜을 구현해야 한다.

### 5. 슬라이스 테스트(Slice Test) 기반 아키텍처 파이프라인 구조

* 계층별 슬라이스 테스트 아키텍처: 애플리케이션 전체를 띄우지 않고 웹 계층, 영속성 계층 등 특정 레이어 한 곳만 얇게 잘라내어(Slice) 검증하는 파이프라인 방식이다.
* 웹 레이어 슬라이스(@WebMvcTest): `DispatcherServlet`, Filter, Interceptor, Controller 등 스프링 웹 MVC 인프라만 컨텍스트에 로드한다. 하부의 무거운 비즈니스 서비스나 데이터베이스 레이어는 `@MockBean`을 통해 완전히 가짜 객체로 대체(Mocking)함으로써, 오직 HTTP 요청 파라미터 검증, 데이터 직렬화, 상태 코드 반환 등 웹 엔드포인트의 제어 책임만을 집중적으로 검증한다.
* 웹 슬라이스 테스트의 명확한 흐름 파이프라인: \[HTTP Client 요청] -> \[Filter / Web 인프라 가동] -> \[DispatcherServlet 접수] -> \[Controller 로직 실행 (하부 Service는 @MockBean 대체)] -> \[JSON 직렬화 및 HTTP 응답 반환]

### 6. MSA 분산 환경에서의 테스트 피라미드 및 계약(Contract) 테스트 기법

* 분산 환경 테스트의 복잡성: 마이크로서비스 아키텍처(MSA) 환경에서는 시스템이 단일 인스턴스가 아닌 수많은 독립된 도메인 인스턴스들의 네트워크 상호작용으로 구동된다. 따라서 단순한 로컬 단독 Unit Test만으로는 시스템의 안정성을 담보하기 어렵다.
* 테스트 피라미드(Test Pyramid) 전략의 단계적 적용: 로컬 트랜잭션 중심의 기저 단위 테스트(Unit Test)를 튼튼하게 다진 후, 타 도메인 서비스의 상태와 인터페이스 결과를 Mock화하여 통합 테스트 범위를 점진적으로 계층화하며 상호작용 범위를 늘려나가는 전략을 취한다.
* Contract Testing (CDC - Consumer Driven Contracts): 분산 환경에서 서비스 간의 API 규격(계약) 파괴를 방지하기 위한 핵심 기법이다. 서비스 제공자(Provider)가 자신이 준수할 수 있는 API 계약 내역을 제시하면, 서비스 사용자(Consumer) 측의 요구사항을 기반으로 계약을 구체화하여 운용 및 테스트를 자동화하는 '소비자 주도 계약 테스트(CDC)' 파이프라인을 가동한다. 이를 통해 연동 중인 타 마이크로서비스를 실제로 띄우지 않고도 상호 간의 API 스펙 일치 여부를 정밀하게 검증한다.
* 성능 테스트(Performance Test) 기법의 다각화:
  * Load Test: K6, JMeter 등의 전문 성능 툴을 활용하여 시스템에 사전에 예측된 기대 부하(Expected Load)를 지속적으로 가하며 처리량을 측정한다.
  * Stress Test: 정상 부하 범위를 넘어 점진적으로 강도를 극단까지 늘려가며 시스템이 무너지는 최외곽 병목 지점(임계점)을 정확히 찾아내고 보완한다.
  * Spike Test: 매우 단시간 내에 폭발적인 초고부하를 한순간에 가하여 시스템의 즉각적인 임계 대응 능력과 이후 스스로 정상 상태로 복구되는 복구 메커니즘을 규명한다.

### 7. TDD 환경에서 Autowired를 활용한 의도적 통합 테스트의 효율성 기법

* Mockito vs Autowired 선택의 본질: Mockito 프레임워크를 사용하여 의존성을 전부 `@Mock`으로 채우고 완벽히 고립된 단위 테스트를 짜는 것은 정밀한 제어가 가능하다는 장점이 있다. 반면, 실제 비즈니스 흐름과 복잡하게 얽힌 다수의 빈(Bean) 객체 간의 유기적 연동을 검증할 때는 모든 빈의 행동을 일일이 `when().thenReturn()`으로 기술해야 하므로 테스트 코드 자체의 작성 오버헤드가 비대해진다.
* 편의성과 효율성 제고를 위한 의도적 @Autowired 활용: TDD 흐름 속에서 Integration Test(통합 테스트)를 구성할 때 실제 스프링 빈 객체를 `@Autowired`로 직접 주입받아 테스트를 수행하는 이유는 구조적·성능적 완벽함을 추구하기 위함이 아니다. 프레임워크가 관리하는 실제 빈 인프라를 그대로 활용함으로써 복잡한 가짜 객체 설정(Mocking) 과정을 생략하고, 보다 편리하고 신속하게 컴포넌트 간의 상호작용 결과를 검증하기 위한 의도적인 아키텍처적 선택 사안이다.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://leehyokyun.gitbook.io/openbook/2-10.-tdd.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
