@su-record/vibe 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +448 -0
- package/agents/backend-python-expert.md +453 -0
- package/agents/database-postgres-expert.md +538 -0
- package/agents/frontend-flutter-expert.md +487 -0
- package/agents/frontend-react-expert.md +424 -0
- package/agents/quality-reviewer.md +542 -0
- package/agents/specification-agent.md +505 -0
- package/bin/sutory +332 -0
- package/bin/vibe +338 -0
- package/mcp/dist/__tests__/complexity.test.js +126 -0
- package/mcp/dist/__tests__/memory.test.js +120 -0
- package/mcp/dist/__tests__/python-dart-complexity.test.js +146 -0
- package/mcp/dist/index.js +230 -0
- package/mcp/dist/lib/ContextCompressor.js +305 -0
- package/mcp/dist/lib/MemoryManager.js +334 -0
- package/mcp/dist/lib/ProjectCache.js +126 -0
- package/mcp/dist/lib/PythonParser.js +241 -0
- package/mcp/dist/tools/browser/browserPool.js +76 -0
- package/mcp/dist/tools/browser/browserUtils.js +135 -0
- package/mcp/dist/tools/browser/inspectNetworkRequests.js +140 -0
- package/mcp/dist/tools/browser/monitorConsoleLogs.js +97 -0
- package/mcp/dist/tools/convention/analyzeComplexity.js +248 -0
- package/mcp/dist/tools/convention/applyQualityRules.js +102 -0
- package/mcp/dist/tools/convention/checkCouplingCohesion.js +233 -0
- package/mcp/dist/tools/convention/complexityMetrics.js +133 -0
- package/mcp/dist/tools/convention/dartComplexity.js +117 -0
- package/mcp/dist/tools/convention/getCodingGuide.js +64 -0
- package/mcp/dist/tools/convention/languageDetector.js +50 -0
- package/mcp/dist/tools/convention/pythonComplexity.js +109 -0
- package/mcp/dist/tools/convention/suggestImprovements.js +257 -0
- package/mcp/dist/tools/convention/validateCodeQuality.js +177 -0
- package/mcp/dist/tools/memory/autoSaveContext.js +79 -0
- package/mcp/dist/tools/memory/database.js +123 -0
- package/mcp/dist/tools/memory/deleteMemory.js +39 -0
- package/mcp/dist/tools/memory/listMemories.js +38 -0
- package/mcp/dist/tools/memory/memoryConfig.js +27 -0
- package/mcp/dist/tools/memory/memorySQLite.js +138 -0
- package/mcp/dist/tools/memory/memoryUtils.js +34 -0
- package/mcp/dist/tools/memory/migrate.js +113 -0
- package/mcp/dist/tools/memory/prioritizeMemory.js +109 -0
- package/mcp/dist/tools/memory/recallMemory.js +40 -0
- package/mcp/dist/tools/memory/restoreSessionContext.js +69 -0
- package/mcp/dist/tools/memory/saveMemory.js +34 -0
- package/mcp/dist/tools/memory/searchMemories.js +37 -0
- package/mcp/dist/tools/memory/startSession.js +100 -0
- package/mcp/dist/tools/memory/updateMemory.js +46 -0
- package/mcp/dist/tools/planning/analyzeRequirements.js +166 -0
- package/mcp/dist/tools/planning/createUserStories.js +119 -0
- package/mcp/dist/tools/planning/featureRoadmap.js +202 -0
- package/mcp/dist/tools/planning/generatePrd.js +156 -0
- package/mcp/dist/tools/prompt/analyzePrompt.js +145 -0
- package/mcp/dist/tools/prompt/enhancePrompt.js +105 -0
- package/mcp/dist/tools/semantic/findReferences.js +195 -0
- package/mcp/dist/tools/semantic/findSymbol.js +200 -0
- package/mcp/dist/tools/thinking/analyzeProblem.js +50 -0
- package/mcp/dist/tools/thinking/breakDownProblem.js +140 -0
- package/mcp/dist/tools/thinking/createThinkingChain.js +39 -0
- package/mcp/dist/tools/thinking/formatAsPlan.js +73 -0
- package/mcp/dist/tools/thinking/stepByStepAnalysis.js +58 -0
- package/mcp/dist/tools/thinking/thinkAloudProcess.js +75 -0
- package/mcp/dist/tools/time/getCurrentTime.js +61 -0
- package/mcp/dist/tools/ui/previewUiAscii.js +232 -0
- package/mcp/dist/types/tool.js +2 -0
- package/mcp/package.json +53 -0
- package/package.json +49 -0
- package/scripts/install-mcp.js +48 -0
- package/scripts/install.sh +70 -0
- package/skills/core/communication-guide.md +104 -0
- package/skills/core/development-philosophy.md +53 -0
- package/skills/core/quick-start.md +121 -0
- package/skills/languages/dart-flutter.md +509 -0
- package/skills/languages/python-fastapi.md +386 -0
- package/skills/languages/typescript-nextjs.md +441 -0
- package/skills/languages/typescript-react-native.md +446 -0
- package/skills/languages/typescript-react.md +525 -0
- package/skills/quality/checklist.md +276 -0
- package/skills/quality/testing-strategy.md +437 -0
- package/skills/standards/anti-patterns.md +369 -0
- package/skills/standards/code-structure.md +291 -0
- package/skills/standards/complexity-metrics.md +312 -0
- package/skills/standards/naming-conventions.md +198 -0
- package/skills/tools/mcp-hi-ai-guide.md +665 -0
- package/skills/tools/mcp-workflow.md +51 -0
- package/templates/constitution-template.md +193 -0
- package/templates/plan-template.md +237 -0
- package/templates/spec-template.md +142 -0
- package/templates/tasks-template.md +132 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# ✅ 최종 검증 체크리스트
|
|
2
|
+
|
|
3
|
+
## 5.1 향상된 코드 품질 체크
|
|
4
|
+
|
|
5
|
+
### 최우선 순위
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
const topPriority = {
|
|
9
|
+
obeysTheGoldenRule: true, // ✅ 요청 범위만 수정
|
|
10
|
+
preservesWorkingCode: true, // ✅ 기존 코드 보존
|
|
11
|
+
respectsExistingStyle: true, // ✅ 기존 스타일 유지
|
|
12
|
+
};
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### 타입 안전성
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
const typeSafety = {
|
|
19
|
+
noAnyType: true, // ✅ any 타입 사용 금지
|
|
20
|
+
strictNullCheck: true, // ✅ null/undefined 체크
|
|
21
|
+
properTypeGuards: true, // ✅ 타입 가드 사용
|
|
22
|
+
genericTypesWhenNeeded: true, // ✅ 제네릭 타입 활용
|
|
23
|
+
};
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 코드 구조 & 복잡도
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
const codeStructure = {
|
|
30
|
+
singleResponsibility: true, // ✅ 단일 책임 원칙
|
|
31
|
+
functionUnder20Lines: true, // ✅ 함수 20줄 이하
|
|
32
|
+
maxNesting3Levels: true, // ✅ 최대 중첩 3단계
|
|
33
|
+
cyclomaticComplexity: 10, // ✅ 순환 복잡도 ≤ 10
|
|
34
|
+
cognitiveComplexity: 15, // ✅ 인지 복잡도 ≤ 15
|
|
35
|
+
maxParameters: 5, // ✅ 매개변수 최대 5개
|
|
36
|
+
componentUnder50Lines: true, // ✅ 컴포넌트 JSX 50줄 이하
|
|
37
|
+
};
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Halstead 메트릭
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
const halsteadMetrics = {
|
|
44
|
+
vocabulary: true, // ✅ 연산자/피연산자 다양성
|
|
45
|
+
difficulty: true, // ✅ 코드 이해 난이도
|
|
46
|
+
effort: true, // ✅ 정신적 노력
|
|
47
|
+
lowComplexity: true, // ✅ 낮은 복잡도 유지
|
|
48
|
+
};
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 결합도 & 응집도
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
const couplingCohesion = {
|
|
55
|
+
looseCoupling: true, // ✅ 느슨한 결합 (≤ 7 dependencies)
|
|
56
|
+
highCohesion: true, // ✅ 높은 응집도 (관련 기능만 모음)
|
|
57
|
+
noCircularDeps: true, // ✅ 순환 의존성 없음
|
|
58
|
+
dependencyInjection: true, // ✅ 의존성 주입 패턴
|
|
59
|
+
};
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 에러 처리
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
const errorHandling = {
|
|
66
|
+
hasErrorHandling: true, // ✅ try-catch/error state
|
|
67
|
+
hasLoadingState: true, // ✅ 로딩 상태
|
|
68
|
+
hasFallbackUI: true, // ✅ 폴백 UI
|
|
69
|
+
properErrorMessages: true, // ✅ 명확한 에러 메시지
|
|
70
|
+
errorBoundaries: true, // ✅ Error Boundary 사용
|
|
71
|
+
};
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 접근성 (Accessibility)
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
const accessibility = {
|
|
78
|
+
hasAriaLabels: true, // ✅ ARIA 레이블
|
|
79
|
+
keyboardAccessible: true, // ✅ 키보드 접근성
|
|
80
|
+
semanticHTML: true, // ✅ 시맨틱 HTML
|
|
81
|
+
focusManagement: true, // ✅ 포커스 관리
|
|
82
|
+
screenReaderFriendly: true, // ✅ 스크린 리더 지원
|
|
83
|
+
};
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 성능
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
const performance = {
|
|
90
|
+
noUnnecessaryRenders: true, // ✅ 불필요한 리렌더 방지
|
|
91
|
+
memoizedExpensive: true, // ✅ 무거운 연산 메모이제이션
|
|
92
|
+
lazyLoading: true, // ✅ 지연 로딩
|
|
93
|
+
batchOperations: true, // ✅ API 호출 배치 처리
|
|
94
|
+
optimizedImages: true, // ✅ 이미지 최적화
|
|
95
|
+
codesplitting: true, // ✅ 코드 스플리팅
|
|
96
|
+
};
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 유지보수성
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
const maintainability = {
|
|
103
|
+
hasJSDoc: true, // ✅ 주요 함수 문서화
|
|
104
|
+
noMagicNumbers: true, // ✅ 매직 넘버 없음
|
|
105
|
+
consistentNaming: true, // ✅ 일관된 네이밍
|
|
106
|
+
properComments: true, // ✅ 적절한 주석
|
|
107
|
+
testable: true, // ✅ 테스트 가능한 구조
|
|
108
|
+
};
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 보안
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
const security = {
|
|
115
|
+
noHardcodedSecrets: true, // ✅ 비밀 정보 하드코딩 금지
|
|
116
|
+
inputValidation: true, // ✅ 입력값 검증
|
|
117
|
+
xssPrevention: true, // ✅ XSS 방지
|
|
118
|
+
csrfProtection: true, // ✅ CSRF 보호
|
|
119
|
+
sqlInjectionPrevention: true, // ✅ SQL Injection 방지
|
|
120
|
+
};
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## 5.2 프로젝트 체크
|
|
124
|
+
|
|
125
|
+
### 의존성 관리
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
const dependencies = {
|
|
129
|
+
noUnusedDeps: true, // ✅ 미사용 패키지 없음
|
|
130
|
+
noDuplicateDeps: true, // ✅ 중복 기능 패키지 없음
|
|
131
|
+
upToDateDeps: true, // ✅ 최신 버전 유지
|
|
132
|
+
securePackages: true, // ✅ 보안 취약점 없음
|
|
133
|
+
};
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 파일 구조
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
const fileStructure = {
|
|
140
|
+
consistentStructure: true, // ✅ 일관된 폴더 구조
|
|
141
|
+
noCircularDeps: true, // ✅ 순환 참조 없음
|
|
142
|
+
logicalGrouping: true, // ✅ 논리적 그룹핑
|
|
143
|
+
clearNaming: true, // ✅ 명확한 파일명
|
|
144
|
+
};
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 번들 최적화
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
const bundleOptimization = {
|
|
151
|
+
treeShaking: true, // ✅ Tree shaking
|
|
152
|
+
codeSplitting: true, // ✅ Code splitting
|
|
153
|
+
lazyLoading: true, // ✅ Lazy loading
|
|
154
|
+
minification: true, // ✅ 최소화
|
|
155
|
+
compression: true, // ✅ 압축 (gzip/brotli)
|
|
156
|
+
};
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## 체크리스트 사용법
|
|
160
|
+
|
|
161
|
+
### 코드 작성 전
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
[ ] 요구사항 명확히 이해
|
|
165
|
+
[ ] 기존 코드 패턴 파악
|
|
166
|
+
[ ] 영향 범위 확인
|
|
167
|
+
[ ] 테스트 계획 수립
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 코드 작성 중
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
[ ] 단일 책임 원칙 준수
|
|
174
|
+
[ ] 함수 길이 20줄 이하 유지
|
|
175
|
+
[ ] 중첩 깊이 3단계 이하
|
|
176
|
+
[ ] 매직 넘버 상수화
|
|
177
|
+
[ ] 타입 안전성 확보
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 코드 작성 후
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
[ ] 타입 체크 통과
|
|
184
|
+
[ ] 린터 경고 없음
|
|
185
|
+
[ ] 테스트 작성 및 통과
|
|
186
|
+
[ ] 문서화 완료
|
|
187
|
+
[ ] 코드 리뷰 준비
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### 커밋 전
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
[ ] 불필요한 코드 제거
|
|
194
|
+
[ ] 콘솔 로그 제거
|
|
195
|
+
[ ] 주석 정리
|
|
196
|
+
[ ] 포맷팅 적용
|
|
197
|
+
[ ] 의미 있는 커밋 메시지 작성
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## 자동 검증 도구
|
|
201
|
+
|
|
202
|
+
### ESLint 설정
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
// .eslintrc.js
|
|
206
|
+
module.exports = {
|
|
207
|
+
rules: {
|
|
208
|
+
'complexity': ['error', 10],
|
|
209
|
+
'max-depth': ['error', 3],
|
|
210
|
+
'max-lines-per-function': ['error', 20],
|
|
211
|
+
'max-params': ['error', 5],
|
|
212
|
+
'no-magic-numbers': ['warn', { ignore: [0, 1, -1] }],
|
|
213
|
+
'@typescript-eslint/no-explicit-any': 'error',
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### TypeScript 설정
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
// tsconfig.json
|
|
222
|
+
{
|
|
223
|
+
"compilerOptions": {
|
|
224
|
+
"strict": true,
|
|
225
|
+
"noImplicitAny": true,
|
|
226
|
+
"strictNullChecks": true,
|
|
227
|
+
"noUnusedLocals": true,
|
|
228
|
+
"noUnusedParameters": true,
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Git Hooks (pre-commit)
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
#!/bin/sh
|
|
237
|
+
# .husky/pre-commit
|
|
238
|
+
|
|
239
|
+
# 타입 체크
|
|
240
|
+
npm run type-check
|
|
241
|
+
|
|
242
|
+
# 린팅
|
|
243
|
+
npm run lint
|
|
244
|
+
|
|
245
|
+
# 테스트
|
|
246
|
+
npm run test
|
|
247
|
+
|
|
248
|
+
# 포맷팅 확인
|
|
249
|
+
npm run format:check
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## 등급 기준
|
|
253
|
+
|
|
254
|
+
| 등급 | 점수 | 설명 |
|
|
255
|
+
|------|------|------|
|
|
256
|
+
| A+ | 95-100 | 완벽한 코드 품질 |
|
|
257
|
+
| A | 90-94 | 우수한 품질 |
|
|
258
|
+
| B+ | 85-89 | 양호한 품질 |
|
|
259
|
+
| B | 80-84 | 개선 권장 |
|
|
260
|
+
| C+ | 75-79 | 개선 필요 |
|
|
261
|
+
| C | 70-74 | 즉시 개선 필요 |
|
|
262
|
+
| F | < 70 | 리팩토링 필요 |
|
|
263
|
+
|
|
264
|
+
## 빠른 체크 (1분)
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
✅ 요청 범위만 수정했는가?
|
|
268
|
+
✅ any 타입이 없는가?
|
|
269
|
+
✅ 함수가 20줄 이하인가?
|
|
270
|
+
✅ 중첩이 3단계 이하인가?
|
|
271
|
+
✅ 에러 처리를 했는가?
|
|
272
|
+
✅ 매직 넘버를 상수화했는가?
|
|
273
|
+
✅ 테스트를 작성했는가?
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
7개 모두 Yes → 배포 가능 ✅
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
# 🧪 AI 시대 테스트 전략
|
|
2
|
+
|
|
3
|
+
## 핵심 원칙
|
|
4
|
+
|
|
5
|
+
```markdown
|
|
6
|
+
✅ 단일 책임 (SRP)
|
|
7
|
+
✅ 중복 코드 제거 (DRY)
|
|
8
|
+
✅ 재사용성 (Reusability)
|
|
9
|
+
✅ 낮은 복잡도 (Low Complexity)
|
|
10
|
+
✅ 계약 우선 설계 (Contract-First)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## AI 주도 개발에서의 테스트 우선순위
|
|
14
|
+
|
|
15
|
+
### 1. Contract Testing (최우선) ⭐⭐⭐
|
|
16
|
+
|
|
17
|
+
**개념**: 코드 작성 전에 **타입/스키마로 계약을 정의**
|
|
18
|
+
|
|
19
|
+
**이유**: AI가 계약을 따라 구현하므로, 타입 안전성이 자동 보장됨
|
|
20
|
+
|
|
21
|
+
#### Python (Pydantic)
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
# 계약 정의 (AI가 이를 따라 구현)
|
|
25
|
+
from pydantic import BaseModel, Field, EmailStr
|
|
26
|
+
|
|
27
|
+
class CreateUserRequest(BaseModel):
|
|
28
|
+
"""사용자 생성 계약"""
|
|
29
|
+
email: EmailStr
|
|
30
|
+
username: str = Field(min_length=3, max_length=50)
|
|
31
|
+
password: str = Field(min_length=8)
|
|
32
|
+
age: int = Field(ge=0, le=150)
|
|
33
|
+
|
|
34
|
+
class UserResponse(BaseModel):
|
|
35
|
+
"""사용자 응답 계약"""
|
|
36
|
+
id: str
|
|
37
|
+
email: str
|
|
38
|
+
username: str
|
|
39
|
+
created_at: str
|
|
40
|
+
|
|
41
|
+
# AI가 이 계약을 위반할 수 없음 (자동 검증)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
#### TypeScript
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// 계약 정의
|
|
48
|
+
interface CreateUserRequest {
|
|
49
|
+
email: string;
|
|
50
|
+
username: string; // 3-50자
|
|
51
|
+
password: string; // 최소 8자
|
|
52
|
+
age: number; // 0-150
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface UserResponse {
|
|
56
|
+
id: string;
|
|
57
|
+
email: string;
|
|
58
|
+
username: string;
|
|
59
|
+
createdAt: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Zod로 런타임 검증
|
|
63
|
+
import { z } from 'zod';
|
|
64
|
+
|
|
65
|
+
const createUserSchema = z.object({
|
|
66
|
+
email: z.string().email(),
|
|
67
|
+
username: z.string().min(3).max(50),
|
|
68
|
+
password: z.string().min(8),
|
|
69
|
+
age: z.number().min(0).max(150),
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### Dart (Flutter)
|
|
74
|
+
|
|
75
|
+
```dart
|
|
76
|
+
// 계약 정의
|
|
77
|
+
class CreateUserRequest {
|
|
78
|
+
const CreateUserRequest({
|
|
79
|
+
required this.email,
|
|
80
|
+
required this.username,
|
|
81
|
+
required this.password,
|
|
82
|
+
required this.age,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
final String email;
|
|
86
|
+
final String username; // 3-50자
|
|
87
|
+
final String password; // 최소 8자
|
|
88
|
+
final int age; // 0-150
|
|
89
|
+
|
|
90
|
+
// JSON 직렬화 (계약 강제)
|
|
91
|
+
Map<String, dynamic> toJson() => {
|
|
92
|
+
'email': email,
|
|
93
|
+
'username': username,
|
|
94
|
+
'password': password,
|
|
95
|
+
'age': age,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 2. Integration Testing (높음) ⭐⭐⭐
|
|
101
|
+
|
|
102
|
+
**개념**: 여러 모듈이 함께 작동하는 **실제 시나리오 테스트**
|
|
103
|
+
|
|
104
|
+
**이유**: AI가 놓친 모듈 간 상호작용 오류를 발견
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
# ✅ 통합 테스트: 실제 비즈니스 흐름
|
|
108
|
+
@pytest.mark.asyncio
|
|
109
|
+
async def test_user_registration_flow():
|
|
110
|
+
"""
|
|
111
|
+
시나리오: 신규 사용자 가입
|
|
112
|
+
1. 이메일 중복 체크
|
|
113
|
+
2. 사용자 생성
|
|
114
|
+
3. 환영 이메일 발송
|
|
115
|
+
4. 기본 설정 생성
|
|
116
|
+
"""
|
|
117
|
+
# Given: 신규 사용자 정보
|
|
118
|
+
request = CreateUserRequest(
|
|
119
|
+
email="new@example.com",
|
|
120
|
+
username="newuser",
|
|
121
|
+
password="password123",
|
|
122
|
+
age=25,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# When: 회원가입 API 호출
|
|
126
|
+
response = await client.post("/api/users", json=request.dict())
|
|
127
|
+
|
|
128
|
+
# Then: 사용자 생성 성공
|
|
129
|
+
assert response.status_code == 201
|
|
130
|
+
data = response.json()
|
|
131
|
+
assert data["email"] == "new@example.com"
|
|
132
|
+
|
|
133
|
+
# And: 환영 이메일 발송 확인
|
|
134
|
+
assert email_service.sent_count == 1
|
|
135
|
+
|
|
136
|
+
# And: 기본 설정 생성 확인
|
|
137
|
+
settings = await get_user_settings(data["id"])
|
|
138
|
+
assert settings is not None
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// ✅ 통합 테스트: React 컴포넌트 + API
|
|
143
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
144
|
+
import userEvent from '@testing-library/user-event';
|
|
145
|
+
import { UserRegistration } from './UserRegistration';
|
|
146
|
+
|
|
147
|
+
test('user can register successfully', async () => {
|
|
148
|
+
// Given: 회원가입 폼 렌더링
|
|
149
|
+
render(<UserRegistration />);
|
|
150
|
+
|
|
151
|
+
// When: 사용자가 폼 입력
|
|
152
|
+
await userEvent.type(screen.getByLabelText('Email'), 'new@example.com');
|
|
153
|
+
await userEvent.type(screen.getByLabelText('Username'), 'newuser');
|
|
154
|
+
await userEvent.type(screen.getByLabelText('Password'), 'password123');
|
|
155
|
+
await userEvent.click(screen.getByRole('button', { name: 'Sign Up' }));
|
|
156
|
+
|
|
157
|
+
// Then: 성공 메시지 표시
|
|
158
|
+
await waitFor(() => {
|
|
159
|
+
expect(screen.getByText('Welcome!')).toBeInTheDocument();
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 3. Property-Based Testing (중간) ⭐⭐
|
|
165
|
+
|
|
166
|
+
**개념**: 입력 범위 전체를 **자동 생성하여 테스트**
|
|
167
|
+
|
|
168
|
+
**이유**: AI가 생각 못한 엣지 케이스를 자동으로 발견
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
# ✅ Property-based testing (Hypothesis)
|
|
172
|
+
from hypothesis import given, strategies as st
|
|
173
|
+
|
|
174
|
+
@given(
|
|
175
|
+
age=st.integers(min_value=0, max_value=150),
|
|
176
|
+
email=st.emails(),
|
|
177
|
+
username=st.text(min_size=3, max_size=50),
|
|
178
|
+
)
|
|
179
|
+
def test_user_creation_with_any_valid_input(age, email, username):
|
|
180
|
+
"""모든 유효한 입력으로 사용자 생성 가능"""
|
|
181
|
+
user = create_user(email=email, username=username, age=age)
|
|
182
|
+
assert user.age == age
|
|
183
|
+
assert user.email == email
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// ✅ Property-based testing (fast-check)
|
|
188
|
+
import fc from 'fast-check';
|
|
189
|
+
|
|
190
|
+
test('discount calculation always returns valid percentage', () => {
|
|
191
|
+
fc.assert(
|
|
192
|
+
fc.property(
|
|
193
|
+
fc.float({ min: 0, max: 10000 }), // 가격
|
|
194
|
+
fc.float({ min: 0, max: 1 }), // 할인율
|
|
195
|
+
(price, rate) => {
|
|
196
|
+
const discount = calculateDiscount(price, rate);
|
|
197
|
+
return discount >= 0 && discount <= price;
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### 4. Unit Testing (낮음, 선택적) ⭐
|
|
205
|
+
|
|
206
|
+
**개념**: 개별 함수/메서드 테스트
|
|
207
|
+
|
|
208
|
+
**언제 작성**: **복잡한 비즈니스 로직만** 선택적으로
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
# ✅ Unit Test: 복잡한 비즈니스 규칙
|
|
212
|
+
def test_tier_selection_score_calculation():
|
|
213
|
+
"""
|
|
214
|
+
대장금 선발 점수 계산 (복잡한 가중치)
|
|
215
|
+
- 피드 ×1.15
|
|
216
|
+
- OCR ×1.2
|
|
217
|
+
- 좋아요 ×1.0
|
|
218
|
+
- 북마크 ×1.0
|
|
219
|
+
- 연계 ×1.5
|
|
220
|
+
"""
|
|
221
|
+
score = calculate_selection_score(
|
|
222
|
+
feeds=10, # 10 × 1.15 = 11.5
|
|
223
|
+
ocr_count=5, # 5 × 1.2 = 6
|
|
224
|
+
likes=20, # 20 × 1.0 = 20
|
|
225
|
+
bookmarks=8, # 8 × 1.0 = 8
|
|
226
|
+
partnerships=2, # 2 × 1.5 = 3
|
|
227
|
+
)
|
|
228
|
+
assert score == 48.5
|
|
229
|
+
|
|
230
|
+
# ❌ 불필요한 Unit Test: 단순 CRUD
|
|
231
|
+
def test_get_user_by_id():
|
|
232
|
+
"""Integration Test로 충분"""
|
|
233
|
+
user = get_user("user-123")
|
|
234
|
+
assert user.id == "user-123" # 의미 없음
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 5. E2E Testing (시나리오 검증) ⭐⭐
|
|
238
|
+
|
|
239
|
+
**개념**: 사용자 관점의 전체 시나리오 테스트
|
|
240
|
+
|
|
241
|
+
**언제**: 주요 사용자 플로우만 선택적으로
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
// ✅ E2E Test: Playwright/Cypress
|
|
245
|
+
test('user can complete full registration flow', async ({ page }) => {
|
|
246
|
+
// 1. 홈페이지 접속
|
|
247
|
+
await page.goto('https://app.example.com');
|
|
248
|
+
|
|
249
|
+
// 2. 회원가입 클릭
|
|
250
|
+
await page.click('text=Sign Up');
|
|
251
|
+
|
|
252
|
+
// 3. 폼 입력
|
|
253
|
+
await page.fill('input[name="email"]', 'test@example.com');
|
|
254
|
+
await page.fill('input[name="username"]', 'testuser');
|
|
255
|
+
await page.fill('input[name="password"]', 'password123');
|
|
256
|
+
|
|
257
|
+
// 4. 제출
|
|
258
|
+
await page.click('button[type="submit"]');
|
|
259
|
+
|
|
260
|
+
// 5. 대시보드로 리다이렉트 확인
|
|
261
|
+
await expect(page).toHaveURL('/dashboard');
|
|
262
|
+
await expect(page.locator('h1')).toContainText('Welcome, testuser!');
|
|
263
|
+
});
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## 테스트 우선순위 결정 트리
|
|
267
|
+
|
|
268
|
+
```
|
|
269
|
+
새 기능 개발 시:
|
|
270
|
+
|
|
271
|
+
1. Contract 정의했는가?
|
|
272
|
+
No → Contract 먼저 작성 (Pydantic/Zod/Dart class)
|
|
273
|
+
Yes → ⬇️
|
|
274
|
+
|
|
275
|
+
2. 여러 모듈이 협력하는가?
|
|
276
|
+
Yes → Integration Test 작성 ⭐⭐⭐
|
|
277
|
+
No → ⬇️
|
|
278
|
+
|
|
279
|
+
3. 복잡한 비즈니스 로직인가? (복잡도 > 10)
|
|
280
|
+
Yes → Unit Test 작성 ⭐
|
|
281
|
+
No → ⬇️
|
|
282
|
+
|
|
283
|
+
4. 핵심 사용자 플로우인가?
|
|
284
|
+
Yes → E2E Test 작성 ⭐⭐
|
|
285
|
+
No → 완료 ✅
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## AI 시대의 TDD 대안: ATDD (AI-Test-Driven Development)
|
|
289
|
+
|
|
290
|
+
```markdown
|
|
291
|
+
# 새로운 개발 흐름
|
|
292
|
+
|
|
293
|
+
1. **요구사항 명확화** (개발자)
|
|
294
|
+
"프리미엄 사용자는 10% 할인을 받는다"
|
|
295
|
+
|
|
296
|
+
2. **Contract 정의** (개발자)
|
|
297
|
+
interface DiscountRequest {
|
|
298
|
+
userId: string;
|
|
299
|
+
orderTotal: number;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
interface DiscountResponse {
|
|
303
|
+
originalPrice: number;
|
|
304
|
+
discountedPrice: number;
|
|
305
|
+
discountRate: number;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
3. **테스트 시나리오 작성** (개발자 or AI)
|
|
309
|
+
test('premium user gets 10% discount', () => {
|
|
310
|
+
// Given: 프리미엄 유저, 100원 주문
|
|
311
|
+
// When: 할인 계산
|
|
312
|
+
// Then: 90원 (10% 할인)
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
4. **AI가 구현** (AI)
|
|
316
|
+
- Contract를 따라 코드 생성
|
|
317
|
+
- 테스트 통과하는 코드 작성
|
|
318
|
+
|
|
319
|
+
5. **통합 테스트** (자동)
|
|
320
|
+
- CI/CD에서 전체 시나리오 검증
|
|
321
|
+
|
|
322
|
+
6. **리팩토링** (AI + 개발자)
|
|
323
|
+
- 복잡도, 중복 제거
|
|
324
|
+
- SRP 준수 확인
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## 언어별 도구
|
|
328
|
+
|
|
329
|
+
### Python
|
|
330
|
+
```bash
|
|
331
|
+
# Contract Testing
|
|
332
|
+
pip install pydantic
|
|
333
|
+
|
|
334
|
+
# Integration Testing
|
|
335
|
+
pip install pytest pytest-asyncio httpx
|
|
336
|
+
|
|
337
|
+
# Property-Based Testing
|
|
338
|
+
pip install hypothesis
|
|
339
|
+
|
|
340
|
+
# Coverage
|
|
341
|
+
pip install pytest-cov
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### TypeScript/JavaScript
|
|
345
|
+
```bash
|
|
346
|
+
# Contract Testing
|
|
347
|
+
npm install zod
|
|
348
|
+
|
|
349
|
+
# Integration Testing
|
|
350
|
+
npm install @testing-library/react @testing-library/user-event
|
|
351
|
+
|
|
352
|
+
# Property-Based Testing
|
|
353
|
+
npm install fast-check
|
|
354
|
+
|
|
355
|
+
# E2E Testing
|
|
356
|
+
npm install playwright
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Dart/Flutter
|
|
360
|
+
```bash
|
|
361
|
+
# Integration Testing
|
|
362
|
+
flutter pub add integration_test
|
|
363
|
+
|
|
364
|
+
# Widget Testing
|
|
365
|
+
flutter test
|
|
366
|
+
|
|
367
|
+
# E2E Testing (Flutter Driver)
|
|
368
|
+
flutter drive --target=test_driver/app.dart
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## 안티패턴
|
|
372
|
+
|
|
373
|
+
```python
|
|
374
|
+
# ❌ 구현 세부사항 테스트 (깨지기 쉬움)
|
|
375
|
+
def test_internal_cache_structure():
|
|
376
|
+
service = UserService()
|
|
377
|
+
assert service._cache == {} # 내부 구현에 의존
|
|
378
|
+
|
|
379
|
+
# ✅ 공개 API 테스트 (견고함)
|
|
380
|
+
def test_user_data_is_cached_after_first_call():
|
|
381
|
+
service = UserService()
|
|
382
|
+
user1 = service.get_user("123")
|
|
383
|
+
user2 = service.get_user("123")
|
|
384
|
+
assert user1 is user2 # 동작만 검증
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
// ❌ 모든 함수에 Unit Test (과도함)
|
|
389
|
+
test('add function adds two numbers', () => {
|
|
390
|
+
expect(add(1, 2)).toBe(3); // 의미 없음
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// ✅ 복잡한 로직만 테스트
|
|
394
|
+
test('calculate shipping cost with multiple conditions', () => {
|
|
395
|
+
const cost = calculateShipping({
|
|
396
|
+
weight: 10,
|
|
397
|
+
distance: 500,
|
|
398
|
+
isPremium: true,
|
|
399
|
+
isExpress: false,
|
|
400
|
+
});
|
|
401
|
+
expect(cost).toBe(45); // 복잡한 규칙 검증
|
|
402
|
+
});
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## 테스트 커버리지 목표
|
|
406
|
+
|
|
407
|
+
```markdown
|
|
408
|
+
# 현실적인 목표
|
|
409
|
+
|
|
410
|
+
- Contract Coverage: 100% (모든 API는 스키마 정의)
|
|
411
|
+
- Integration Coverage: 80% (주요 비즈니스 흐름)
|
|
412
|
+
- Unit Coverage: 선택적 (복잡한 로직만)
|
|
413
|
+
- E2E Coverage: 20-30% (핵심 사용자 플로우)
|
|
414
|
+
|
|
415
|
+
# ❌ 피해야 할 것
|
|
416
|
+
- 100% Unit Test Coverage (시간 낭비)
|
|
417
|
+
- 단순 CRUD에 Unit Test (Integration으로 충분)
|
|
418
|
+
- 모든 엣지 케이스 수동 테스트 (Property-based 사용)
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## 핵심 요약
|
|
422
|
+
|
|
423
|
+
```markdown
|
|
424
|
+
AI 시대 테스트 전략:
|
|
425
|
+
|
|
426
|
+
1. ✅ Contract-First (타입/스키마 먼저)
|
|
427
|
+
2. ✅ Integration Testing (실제 시나리오)
|
|
428
|
+
3. ⚠️ Unit Testing (복잡한 로직만)
|
|
429
|
+
4. ❌ 전통적 TDD (AI 시대엔 비효율)
|
|
430
|
+
|
|
431
|
+
목표:
|
|
432
|
+
- 단일 책임 (SRP)
|
|
433
|
+
- 중복 제거 (DRY)
|
|
434
|
+
- 재사용성
|
|
435
|
+
- 낮은 복잡도
|
|
436
|
+
- 빠른 피드백
|
|
437
|
+
```
|