1.TDD 기본 이해

1) TDD 목적  
   코드 품질 향상으로 유지보수 비용 절감
- 설계 품질 향상: 테스트를 먼저 작성하면서 코드 구조와 인터페이스를 먼저 고민
- 회귀 버그 방지: 테스트 자동화로 코드 변경 시 기존 기능의 오작동을 빠르게 감지
- 리팩토링 검증: 코드 개선 후 테스트 코드로 검증할 수 있어 리팩토링에 대한 자신감 확보
- 살아있는 문서: 테스트 코드에 샘플 데이터를 이용한 예시가 있으므로 실제 코드의 동작 방식을 문서화

---  

2) 테스트 유형
- 단위 테스트(Unit Test): 외부 기술요소(DB, 웹서버 등)와의 인터페이스 없이 단위 클래스의 퍼블릭 메소드 테스트
- 통합 테스트(Integration Test): 일부 아키텍처 영역에서 외부 기술 요소와 인터페이스까지 테스트
- E2E 테스트(E2E Test): 모든 아키텍처 영역에서 외부 기술 요소와 인터페이스를 테스트

* 아키텍처 영역: 클래스를 아키텍처적으로 나눈 레이어를 의미함(예: controller, service, domain, repository)

---

3) 테스트 피라미드

- 단위 테스트 70%, 통합 테스트 20%, E2E 테스트 10%의 비율로 권장
- Mike Cohn이 "Succeeding with Agile"에서 처음 제시한 개념
- 단위 테스트에서 E2E 테스트로 가면서 속도는 느려지고 비용은 높아짐

---

4) Red-Green-Refactor 사이클

Red-Green-Refactor는 TDD(Test-Driven Development)를 수행하는 핵심 사이클임
- Red (실패하는 테스트 작성)
    - 새로운 기능에 대한 테스트 코드를 먼저 작성
    - 아직 구현이 없으므로 테스트는 실패
    - 이 단계에서 기능의 인터페이스를 설계
- Green (테스트 통과하는 코드 작성)
    - 테스트를 통과하는 최소한의 코드 작성
    - 품질보다는 동작에 초점
- Refactor (리팩토링)
    - 중복 제거, 가독성 개선
    - 테스트는 계속 통과하도록 유지
    - 코드 품질 개선

---
2. 테스트 전략

1) 테스트 수행 원칙: FIRST 원칙
- Fast: 테스트는 빠르게 실행되어야 함
- Isolated: 각 테스트는 독립적이어야 함
- Repeatable: 어떤 환경에서도 동일한 결과가 나와야 함
- Self-validating: 테스트는 성공/실패가 명확해야 함
- Timely: 테스트는 실제 코드 작성 전/직후에 작성되어야 함

---

2) 공통 전략: 테스트 코드 작성 관련
- 한 테스트는 한 가지만 테스트
- Given-When-Then 패턴 사용
    - Given(준비): 테스트에 필요한 상태와 데이터를 설정
    - When(실행): 테스트하려는 동작을 수행
    - Then(검증): 기대하는 결과가 나왔는지 확인
- 깨끗한 테스트 코드 작성
    - 테스트 의도를 명확히 하는 네이밍
    - 테스트 케이스는 시나리오 중심으로 구성
    - 공통 설정은 별도 메서드로 분리
    - 매직넘버 대신 상수 사용
    - 테스트 데이터는 최소한으로 사용
- 경계값 테스트가 중요
    - null 값
    - 빈 컬렉션
    - 최대/최소값
    - 0이나 1과 같은 특수값
    - 잘못된 포맷의 입력값

---

2) 공통 전략: 테스트 코드 관리 관련
- 비용 효율적인 테스트 전략
    - 자주 변경되는 비즈니스 로직에 대한 테스트 강화
    - 실제 운영 환경과 유사한 통합 테스트 구성
    - 테스트 실행 시간과 리소스 사용량 모니터링
- 지속적인 테스트 개선
    - 테스트 커버리지보다 테스트 품질 중시
    - 깨진 테스트는 즉시 수정하는 문화 정착
    - 테스트 코드도 실제 코드만큼 중요하게 관리
- 팀 협업을 위한 가이드라인 수립
    - 테스트 네이밍 컨벤션 수립
    - 테스트 데이터 관리 전략 합의
    - 테스트 실패 시 대응 프로세스 수립

---

3) 단위 테스트 전략
- 테스트 범위 명확화
    - 클래스의 각 public 메소드가 수행하는 단일 책임을 검증
    - private 메서드는 public 메서드를 통해 간접적으로 테스트
- 외부 의존성 처리
    - DB, 파일, 네트워크 등 외부 시스템은 가짜 객체로 대체(Mocking)
    - 테스트 더블(스턴트맨을 Stunt Double이라고 함. 대역으로 이해)은 꼭 필요한 동작만 구현
        - Mock: 메소드 호출 여부와 파라미터 검증
        - Stub: 반환값의 일치 여부 검증
        - Spy: Mocking하지 않고 실제 메소드를 감싸서 호출횟수, 호출순서등 추가 정보 검증
- 격리성 확보
    - 테스트 간 상호 영향 없도록 설계: 동일 공유 자원/객체를 사용하지 않게 함
    - 테스트 실행 순서와 무관하게 동작
- 가독성과 유지보수성
    - 테스트 대상 클래스당 하나의 테스트 클래스
    - 테스트 메서드는 한 가지 시나리오만 검증

---

4) 단위 테스트 시 Mocking 전략
- 외부 시스템(DB, 외부 API 등)은 반드시 Mocking
- 같은 레이어의 의존성 있는 클래스는 실제 객체 사용
- 예외적으로 의존 객체가 매우 복잡하거나 무거운 경우 Mocking 고려

* 참고: 모의 객체 테스트 균형점 찾기  
  출처: When to mocking by Uncle Bob(https://blog.cleancoder.com/uncle-bob/2014/05/10/WhenToMock.html)
- 모의 객체를 이용 안 하면: 테스트가 오래 걸리고 결과를 신뢰하기 어려우며 인프라에 너무 많은 영향을 받음
- 모의 객체를 지나치게 사용하면: 복잡하고 수정에 영향을 너무 많이 받으며 모의 인터페이스가 폭발적으로 증가
- 균형점 찾기
    - 아키텍처적으로 중요한 경계에서만 모의 테스트를 수행하고, 그 경계 안에서는 하지 않는다.  
      (Mock across architecturally significant boundaries, but not within those boundaries.)
    - 여기서 경계란 Controller, Service, Repository, Domain등의 레이어를 의미함

---
5) 통합 테스트 전략
- 웹 서버 인터페이스
    - @WebMvcTest, @WebFluxTest 활용
    - Controller 계층의 요청/응답 검증
    - Service 계층은 Mocking 처리

- Database 인터페이스
    - @DataJpaTest 활용
    - TestContainer로 실제 DB 엔진 실행

- 외부 서비스 인터페이스
    - WireMock 등을 활용한 Mocking
    - 실제 API 스펙 기반 테스트

- 테스트 환경 구성
    - 테스트용 별도 설정 파일 구성
    - 테스트 데이터는 테스트 시작 시 초기화
    - @Transactional을 활용한 테스트 격리
    - 테스트 간 독립성 보장

---
6) E2E 테스트 전략
- 원칙
    - 단위 테스트나 컴포넌트 테스트에서 놓칠 수 있는 시나리오를 찾아내는 것이 목표임
    - 조건별 로직이나 분기 상황(edge cases)이 아닌 상위 수준의 일반적인 시나리오만 테스트
    - 만약 어떤 시스템 테스트 시나리오가 실패 했는데 단위 테스트나 통합 테스트가 없다면 만들어야 함

- 운영과 동일한 테스트 환경 구성: 웹서버/WAS, DB, 캐시, MQ, 외부시스템
- 테스트 데이터 관리
    - 테스트용 마스터 데이터 구성
    - 시나리오별 테스트 데이터 세트 준비
    - 데이터 초기화 및 정리 자동화
- 테스트 자동화 전략
    - UI 테스트: Selenium, Cucumber, Playwright 등 도구 활용
    - API 테스트: Rest-Assured, Postman 등 도구 활용

---

7) 테스트 코드 네이밍 컨벤션

- 패키지 네이밍
```
[Syntax]
{프로덕션패키지}.test.{테스트유형}

[Example]
- 단위테스트: com.company.order.test.unit
- 통합테스트: com.company.order.test.integration
- E2E테스트: com.company.order.test.e2e
```

- 클래스 네이밍
```
[Syntax]
{대상클래스}{테스트유형}Test

[Example]
- 단위테스트: OrderServiceUnitTest
- 통합테스트: OrderServiceIntegrationTest
- E2E테스트: OrderServiceE2ETest
```

- 메소드 네이밍
```
[Syntax]
given{초기상태}_when{행위}_then{결과}

[Example]
givenEmptyCart_whenAddItem_thenSuccess()
givenInvalidToken_whenAuthenticate_thenThrowException()
givenExistingUser_whenUpdateProfile_thenProfileUpdated()
```

- 테스트 데이터 네이밍
```
[Syntax]
상수: {상태}_{대상}
변수: {상태}{대상}

[Example]
// 상수
VALID_USER_ID = 1L
EMPTY_ORDER_LIST = Collections.emptyList()

// 변수
normalUser = new User(...)
emptyCart = new Cart()
```
