mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-01 22:53:27 +08:00
327 lines
8.8 KiB
Markdown
327 lines
8.8 KiB
Markdown
---
|
|
description: 테스트 주도 개발 워크플로우 강제. 인터페이스를 스캐폴딩하고, 테스트를 먼저 생성한 후 통과할 최소한의 코드를 구현합니다. 80% 이상 커버리지를 보장합니다.
|
|
---
|
|
|
|
# TDD 커맨드
|
|
|
|
이 커맨드는 **tdd-guide** 에이전트를 호출하여 테스트 주도 개발 방법론을 강제합니다.
|
|
|
|
## 이 커맨드가 하는 것
|
|
|
|
1. **인터페이스 스캐폴딩** - 타입/인터페이스를 먼저 정의
|
|
2. **테스트 먼저 생성** - 실패하는 테스트 작성 (RED)
|
|
3. **최소한의 코드 구현** - 통과하기에 충분한 코드만 작성 (GREEN)
|
|
4. **리팩토링** - 테스트를 통과시키면서 코드 개선 (REFACTOR)
|
|
5. **커버리지 확인** - 80% 이상 테스트 커버리지 보장
|
|
|
|
## 사용 시점
|
|
|
|
`/tdd`를 사용해야 할 때:
|
|
- 새 기능 구현
|
|
- 새 함수/컴포넌트 추가
|
|
- 버그 수정 (버그를 재현하는 테스트를 먼저 작성)
|
|
- 기존 코드 리팩토링
|
|
- 핵심 비즈니스 로직 구현
|
|
|
|
## 작동 방식
|
|
|
|
tdd-guide 에이전트가 수행하는 작업:
|
|
|
|
1. 입출력에 대한 **인터페이스 정의**
|
|
2. (코드가 아직 존재하지 않으므로) **실패하는 테스트 작성**
|
|
3. 올바른 이유로 실패하는지 **테스트 실행** 및 확인
|
|
4. 테스트를 통과하도록 **최소한의 구현 작성**
|
|
5. 통과하는지 **테스트 실행** 및 확인
|
|
6. 테스트를 통과시키면서 코드 **리팩토링**
|
|
7. **커버리지 확인** 및 80% 미만이면 테스트 추가
|
|
|
|
## TDD 사이클
|
|
|
|
```
|
|
RED → GREEN → REFACTOR → REPEAT
|
|
|
|
RED: 실패하는 테스트 작성
|
|
GREEN: 통과할 최소한의 코드 작성
|
|
REFACTOR: 코드 개선, 테스트 계속 통과 유지
|
|
REPEAT: 다음 기능/시나리오
|
|
```
|
|
|
|
## 사용 예시
|
|
|
|
````
|
|
User: /tdd 마켓 유동성 점수를 계산하는 함수가 필요합니다
|
|
|
|
Agent (tdd-guide):
|
|
# TDD 세션: 마켓 유동성 점수 계산기
|
|
|
|
## 1단계: 인터페이스 정의 (SCAFFOLD)
|
|
|
|
```typescript
|
|
// lib/liquidity.ts
|
|
export interface MarketData {
|
|
totalVolume: number
|
|
bidAskSpread: number
|
|
activeTraders: number
|
|
lastTradeTime: Date
|
|
}
|
|
|
|
export function calculateLiquidityScore(market: MarketData): number {
|
|
// TODO: 구현
|
|
throw new Error('Not implemented')
|
|
}
|
|
```
|
|
|
|
## 2단계: 실패하는 테스트 작성 (RED)
|
|
|
|
```typescript
|
|
// lib/liquidity.test.ts
|
|
import { calculateLiquidityScore } from './liquidity'
|
|
|
|
describe('calculateLiquidityScore', () => {
|
|
it('should return high score for liquid market', () => {
|
|
const market = {
|
|
totalVolume: 100000,
|
|
bidAskSpread: 0.01,
|
|
activeTraders: 500,
|
|
lastTradeTime: new Date()
|
|
}
|
|
|
|
const score = calculateLiquidityScore(market)
|
|
|
|
expect(score).toBeGreaterThan(80)
|
|
expect(score).toBeLessThanOrEqual(100)
|
|
})
|
|
|
|
it('should return low score for illiquid market', () => {
|
|
const market = {
|
|
totalVolume: 100,
|
|
bidAskSpread: 0.5,
|
|
activeTraders: 2,
|
|
lastTradeTime: new Date(Date.now() - 86400000) // 1일 전
|
|
}
|
|
|
|
const score = calculateLiquidityScore(market)
|
|
|
|
expect(score).toBeLessThan(30)
|
|
expect(score).toBeGreaterThanOrEqual(0)
|
|
})
|
|
|
|
it('should handle edge case: zero volume', () => {
|
|
const market = {
|
|
totalVolume: 0,
|
|
bidAskSpread: 0,
|
|
activeTraders: 0,
|
|
lastTradeTime: new Date()
|
|
}
|
|
|
|
const score = calculateLiquidityScore(market)
|
|
|
|
expect(score).toBe(0)
|
|
})
|
|
})
|
|
```
|
|
|
|
## 3단계: 테스트 실행 - 실패 확인
|
|
|
|
```bash
|
|
npm test lib/liquidity.test.ts
|
|
|
|
FAIL lib/liquidity.test.ts
|
|
✕ should return high score for liquid market (2 ms)
|
|
Error: Not implemented
|
|
|
|
1 test failed, 0 passed
|
|
```
|
|
|
|
✅ 예상대로 테스트 실패. 구현 준비 완료.
|
|
|
|
## 4단계: 최소한의 코드 구현 (GREEN)
|
|
|
|
```typescript
|
|
// lib/liquidity.ts
|
|
export function calculateLiquidityScore(market: MarketData): number {
|
|
// 거래량 0인 엣지 케이스 처리
|
|
if (market.totalVolume === 0) {
|
|
return 0
|
|
}
|
|
|
|
// 구성 요소별 점수 계산 (0-100 스케일)
|
|
const volumeScore = Math.min(market.totalVolume / 1000, 100)
|
|
const spreadScore = Math.max(100 - (market.bidAskSpread * 1000), 0)
|
|
const traderScore = Math.min(market.activeTraders / 10, 100)
|
|
|
|
// 최근 활동 보너스
|
|
const hoursSinceLastTrade = (Date.now() - market.lastTradeTime.getTime()) / (1000 * 60 * 60)
|
|
const recencyScore = Math.max(100 - (hoursSinceLastTrade * 10), 0)
|
|
|
|
// 가중 평균
|
|
const score = (
|
|
volumeScore * 0.4 +
|
|
spreadScore * 0.3 +
|
|
traderScore * 0.2 +
|
|
recencyScore * 0.1
|
|
)
|
|
|
|
return Math.min(Math.max(score, 0), 100) // 0-100으로 클램핑
|
|
}
|
|
```
|
|
|
|
## 5단계: 테스트 실행 - 통과 확인
|
|
|
|
```bash
|
|
npm test lib/liquidity.test.ts
|
|
|
|
PASS lib/liquidity.test.ts
|
|
✓ should return high score for liquid market (3 ms)
|
|
✓ should return low score for illiquid market (2 ms)
|
|
✓ should handle edge case: zero volume (1 ms)
|
|
|
|
3 tests passed
|
|
```
|
|
|
|
✅ 모든 테스트 통과!
|
|
|
|
## 6단계: 리팩토링 (IMPROVE)
|
|
|
|
```typescript
|
|
// lib/liquidity.ts - 상수와 가독성 향상을 위한 리팩토링
|
|
const WEIGHTS = {
|
|
VOLUME: 0.4,
|
|
SPREAD: 0.3,
|
|
TRADERS: 0.2,
|
|
RECENCY: 0.1,
|
|
} as const
|
|
|
|
const SCALE_FACTORS = {
|
|
VOLUME: 1000,
|
|
SPREAD: 1000,
|
|
TRADERS: 10,
|
|
RECENCY_PENALTY: 10,
|
|
} as const
|
|
|
|
function clamp(value: number, min: number, max: number): number {
|
|
return Math.min(Math.max(value, min), max)
|
|
}
|
|
|
|
export function calculateLiquidityScore(market: MarketData): number {
|
|
if (market.totalVolume === 0) return 0
|
|
|
|
const volumeScore = Math.min(market.totalVolume / SCALE_FACTORS.VOLUME, 100)
|
|
const spreadScore = clamp(100 - (market.bidAskSpread * SCALE_FACTORS.SPREAD), 0, 100)
|
|
const traderScore = Math.min(market.activeTraders / SCALE_FACTORS.TRADERS, 100)
|
|
|
|
const hoursSinceLastTrade = (Date.now() - market.lastTradeTime.getTime()) / (1000 * 60 * 60)
|
|
const recencyScore = clamp(100 - (hoursSinceLastTrade * SCALE_FACTORS.RECENCY_PENALTY), 0, 100)
|
|
|
|
const weightedScore =
|
|
volumeScore * WEIGHTS.VOLUME +
|
|
spreadScore * WEIGHTS.SPREAD +
|
|
traderScore * WEIGHTS.TRADERS +
|
|
recencyScore * WEIGHTS.RECENCY
|
|
|
|
return clamp(weightedScore, 0, 100)
|
|
}
|
|
```
|
|
|
|
## 7단계: 테스트가 여전히 통과하는지 확인
|
|
|
|
```bash
|
|
npm test lib/liquidity.test.ts
|
|
|
|
PASS lib/liquidity.test.ts
|
|
✓ should return high score for liquid market (3 ms)
|
|
✓ should return low score for illiquid market (2 ms)
|
|
✓ should handle edge case: zero volume (1 ms)
|
|
|
|
3 tests passed
|
|
```
|
|
|
|
✅ 리팩토링 완료, 테스트 여전히 통과!
|
|
|
|
## 8단계: 커버리지 확인
|
|
|
|
```bash
|
|
npm test -- --coverage lib/liquidity.test.ts
|
|
|
|
File | % Stmts | % Branch | % Funcs | % Lines
|
|
---------------|---------|----------|---------|--------
|
|
liquidity.ts | 100 | 100 | 100 | 100
|
|
|
|
Coverage: 100% ✅ (목표: 80%)
|
|
```
|
|
|
|
✅ TDD 세션 완료!
|
|
````
|
|
|
|
## TDD 모범 사례
|
|
|
|
**해야 할 것:**
|
|
- 구현 전에 테스트를 먼저 작성
|
|
- 구현 전에 테스트를 실행하여 실패하는지 확인
|
|
- 테스트를 통과하기 위한 최소한의 코드 작성
|
|
- 테스트가 통과한 후에만 리팩토링
|
|
- 엣지 케이스와 에러 시나리오 추가
|
|
- 80% 이상 커버리지 목표 (핵심 코드는 100%)
|
|
|
|
**하지 말아야 할 것:**
|
|
- 테스트 전에 구현 작성
|
|
- 각 변경 후 테스트 실행 건너뛰기
|
|
- 한 번에 너무 많은 코드 작성
|
|
- 실패하는 테스트 무시
|
|
- 구현 세부사항 테스트 (동작을 테스트)
|
|
- 모든 것을 mock (통합 테스트 선호)
|
|
|
|
## 포함할 테스트 유형
|
|
|
|
**단위 테스트** (함수 수준):
|
|
- 정상 경로 시나리오
|
|
- 엣지 케이스 (빈 값, null, 최대값)
|
|
- 에러 조건
|
|
- 경계값
|
|
|
|
**통합 테스트** (컴포넌트 수준):
|
|
- API 엔드포인트
|
|
- 데이터베이스 작업
|
|
- 외부 서비스 호출
|
|
- hooks가 포함된 React 컴포넌트
|
|
|
|
**E2E 테스트** (`/e2e` 커맨드 사용):
|
|
- 핵심 사용자 흐름
|
|
- 다단계 프로세스
|
|
- 풀 스택 통합
|
|
|
|
## 커버리지 요구사항
|
|
|
|
- **80% 최소** - 모든 코드에 대해
|
|
- **100% 필수** - 다음 항목에 대해:
|
|
- 금융 계산
|
|
- 인증 로직
|
|
- 보안에 중요한 코드
|
|
- 핵심 비즈니스 로직
|
|
|
|
## 중요 사항
|
|
|
|
**필수**: 테스트는 반드시 구현 전에 작성해야 합니다. TDD 사이클은 다음과 같습니다:
|
|
|
|
1. **RED** - 실패하는 테스트 작성
|
|
2. **GREEN** - 통과하도록 구현
|
|
3. **REFACTOR** - 코드 개선
|
|
|
|
절대 RED 단계를 건너뛰지 마세요. 절대 테스트 전에 코드를 작성하지 마세요.
|
|
|
|
## 다른 커맨드와의 연동
|
|
|
|
- `/plan`을 먼저 사용하여 무엇을 만들지 이해
|
|
- `/tdd`를 사용하여 테스트와 함께 구현
|
|
- `/build-fix`를 사용하여 빌드 에러 발생 시 수정
|
|
- `/code-review`를 사용하여 구현 리뷰
|
|
- `/test-coverage`를 사용하여 커버리지 검증
|
|
|
|
## 관련 에이전트
|
|
|
|
이 커맨드는 `tdd-guide` 에이전트를 호출합니다:
|
|
`~/.claude/agents/tdd-guide.md`
|
|
|
|
그리고 `tdd-workflow` 스킬을 참조할 수 있습니다:
|
|
`~/.claude/skills/tdd-workflow/`
|