@su-record/vibe 2.0.0 โ 2.0.1
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/.claude/agents/explorer.md +48 -48
- package/.claude/agents/implementer.md +53 -53
- package/.claude/agents/searcher.md +54 -54
- package/.claude/agents/simplifier.md +119 -119
- package/.claude/agents/tester.md +49 -49
- package/.claude/commands/vibe.analyze.md +239 -239
- package/.claude/commands/vibe.continue.md +88 -88
- package/.claude/commands/vibe.diagram.md +178 -178
- package/.claude/commands/vibe.reason.md +306 -306
- package/.claude/commands/vibe.run.md +760 -760
- package/.claude/commands/vibe.spec.md +339 -339
- package/.claude/commands/vibe.tool.md +153 -153
- package/.claude/commands/vibe.ui.md +137 -137
- package/.claude/commands/vibe.verify.md +238 -238
- package/.claude/settings.json +152 -152
- package/.claude/settings.local.json +4 -57
- package/.vibe/config.json +9 -0
- package/.vibe/constitution.md +184 -184
- package/.vibe/rules/core/communication-guide.md +104 -104
- package/.vibe/rules/core/development-philosophy.md +52 -52
- package/.vibe/rules/core/quick-start.md +120 -120
- package/.vibe/rules/quality/bdd-contract-testing.md +388 -388
- package/.vibe/rules/quality/checklist.md +276 -276
- package/.vibe/rules/quality/testing-strategy.md +437 -437
- package/.vibe/rules/standards/anti-patterns.md +369 -369
- package/.vibe/rules/standards/code-structure.md +291 -291
- package/.vibe/rules/standards/complexity-metrics.md +312 -312
- package/.vibe/rules/standards/naming-conventions.md +198 -198
- package/.vibe/rules/tools/mcp-hi-ai-guide.md +665 -665
- package/.vibe/rules/tools/mcp-workflow.md +51 -51
- package/.vibe/setup.sh +31 -31
- package/CLAUDE.md +122 -122
- package/LICENSE +21 -21
- package/README.md +568 -568
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +391 -406
- package/dist/cli/index.js.map +1 -1
- package/dist/lib/MemoryManager.js +92 -92
- package/dist/lib/PythonParser.js +108 -108
- package/dist/lib/gemini-mcp.js +15 -15
- package/dist/lib/gemini-oauth.js +35 -35
- package/dist/lib/gpt-mcp.js +17 -17
- package/dist/lib/gpt-oauth.js +44 -44
- package/dist/tools/analytics/getUsageAnalytics.js +12 -12
- package/dist/tools/memory/createMemoryTimeline.js +10 -10
- package/dist/tools/memory/getMemoryGraph.js +12 -12
- package/dist/tools/memory/getSessionContext.js +9 -9
- package/dist/tools/memory/linkMemories.js +14 -14
- package/dist/tools/memory/listMemories.js +4 -4
- package/dist/tools/memory/recallMemory.js +4 -4
- package/dist/tools/memory/saveMemory.js +4 -4
- package/dist/tools/memory/searchMemoriesAdvanced.js +22 -22
- package/dist/tools/planning/generatePrd.js +46 -46
- package/dist/tools/prompt/enhancePromptGemini.js +160 -160
- package/dist/tools/reasoning/applyReasoningFramework.js +56 -56
- package/dist/tools/semantic/analyzeDependencyGraph.js +12 -12
- package/package.json +67 -67
- package/templates/constitution-template.md +184 -184
- package/templates/contract-backend-template.md +517 -517
- package/templates/contract-frontend-template.md +594 -594
- package/templates/feature-template.md +96 -96
- package/templates/hooks-template.json +103 -103
- package/templates/spec-template.md +199 -199
- package/dist/lib/vibe-mcp.d.ts.map +0 -1
- package/dist/lib/vibe-mcp.js.map +0 -1
|
@@ -1,437 +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
|
-
```
|
|
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
|
+
```
|