mirror of
https://github.com/ktds-dg0501/kt-event-marketing.git
synced 2025-12-06 15:26:23 +00:00
215 lines
8.3 KiB
Markdown
215 lines
8.3 KiB
Markdown
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()
|
|
```
|