docs(10_Wiki): 위키 전체 재구성 — Topic_* 폴더를 4개 카테고리로 통합 + 대규모 중복 제거

Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를
Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류.

- 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로
  자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거,
  동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거.
- 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming,
  Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business,
  Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로,
  나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는
  title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백).
  원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지.
- 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서.
- 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는
  지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지.
- Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경.
- 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
This commit is contained in:
Antigravity Agent
2026-07-05 00:33:48 +09:00
parent 1cfd3bbb56
commit 9148c358d0
6455 changed files with 1 additions and 86875 deletions
@@ -0,0 +1,217 @@
---
id: testing-property-based
title: Property-based Testing — fast-check / 자동 input
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [testing, property-based, fast-check, vibe-coding]
tech_stack: { language: "TS / fast-check", applicable_to: ["Backend", "Frontend"] }
applied_in: []
aliases: [property-based testing, fast-check, QuickCheck, Hypothesis, generator, shrinking]
---
# Property-based Testing
> 예시 input 만 테스트 = edge case 놓침. **속성 (property) + 자동 input 생성** = 100+ random case 시도. fast-check (TS), Hypothesis (Python), QuickCheck (Haskell).
## 📖 핵심 개념
- Property: "모든 input X 에 대해 결과 Y" 같은 universal 진리.
- Generator: random input 자동 생성.
- Shrinking: 실패한 input → 최소 reproduce.
## 💻 코드 패턴
### fast-check 기본
```ts
import fc from 'fast-check';
import { describe, test, expect } from 'vitest';
function reverse<T>(arr: T[]): T[] {
return [...arr].reverse();
}
describe('reverse', () => {
test('reverse twice = original', () => {
fc.assert(
fc.property(fc.array(fc.integer()), (arr) => {
expect(reverse(reverse(arr))).toEqual(arr);
})
);
});
test('length preserved', () => {
fc.assert(
fc.property(fc.array(fc.anything()), (arr) => {
expect(reverse(arr).length).toBe(arr.length);
})
);
});
});
```
`fc.assert` 가 100번 (default) random run.
### Generators
```ts
fc.integer({ min: 0, max: 100 })
fc.float({ min: 0, max: 1, noNaN: true })
fc.string({ minLength: 1, maxLength: 100 })
fc.array(fc.integer(), { minLength: 1 })
fc.uuid()
fc.emailAddress()
fc.date({ min: new Date('2020'), max: new Date('2030') })
fc.option(fc.integer()) // T | undefined
// Object
fc.record({
id: fc.uuid(),
email: fc.emailAddress(),
age: fc.integer({ min: 0, max: 120 }),
})
// Tuple
fc.tuple(fc.string(), fc.integer())
// One of
fc.oneof(fc.integer(), fc.string())
```
### Stateful test (model-based)
```ts
import fc, { Command } from 'fast-check';
class Stack<T> {
private arr: T[] = [];
push(x: T) { this.arr.push(x); }
pop(): T | undefined { return this.arr.pop(); }
size() { return this.arr.length; }
}
class PushCommand implements Command<{ size: number }, Stack<number>> {
constructor(readonly value: number) {}
check = (m: { size: number }) => true;
run(m: { size: number }, r: Stack<number>) {
r.push(this.value);
m.size++;
expect(r.size()).toBe(m.size);
}
}
class PopCommand implements Command<{ size: number }, Stack<number>> {
check = (m: { size: number }) => m.size > 0;
run(m, r) {
r.pop();
m.size--;
expect(r.size()).toBe(m.size);
}
}
test('stack', () => {
fc.assert(fc.property(
fc.commands([
fc.integer().map(v => new PushCommand(v)),
fc.constant(new PopCommand()),
], { maxCommands: 100 }),
(cmds) => fc.modelRun(() => ({ model: { size: 0 }, real: new Stack() }), cmds),
));
});
```
### Real-world: JSON serialize
```ts
test('JSON parse(stringify(x)) = x', () => {
fc.assert(
fc.property(fc.jsonValue(), (x) => {
expect(JSON.parse(JSON.stringify(x))).toEqual(x);
})
);
});
```
### 검증
```ts
import { z } from 'zod';
const User = z.object({ email: z.string().email(), age: z.number().int().positive() });
test('valid input always parses', () => {
fc.assert(fc.property(
fc.record({
email: fc.emailAddress(),
age: fc.integer({ min: 1, max: 200 }),
}),
(input) => {
expect(() => User.parse(input)).not.toThrow();
},
));
});
```
### Sort idempotent
```ts
test('sorted is idempotent', () => {
fc.assert(fc.property(
fc.array(fc.integer()),
(arr) => {
const s1 = [...arr].sort();
const s2 = [...s1].sort();
expect(s2).toEqual(s1);
},
));
});
```
### Shrinking 효과
```ts
// 실패 시 input 자동 최소화
// 처음: [42, -123, 0, 9999, ...] (10 items, 실패)
// shrink 후: [0, -1] (2 items, 같은 실패)
// → 디버깅 쉬움
```
### Reproduce 실패
```ts
fc.assert(fc.property(...), { seed: 1234, path: '5:0:1' });
// 같은 seed = 같은 input
```
### 통계 출력
```ts
fc.statistics(
fc.integer(),
(n) => n < 0 ? 'negative' : n === 0 ? 'zero' : 'positive',
100,
);
// 분포 확인 — 충분히 다양?
```
## 🤔 의사결정 기준
| 적합 | 부적합 |
|---|---|
| 순수 함수 | 외부 부수효과 |
| Encode/decode round-trip | UI 상호작용 |
| Sort / search algorithm | API integration |
| Parser / formatter | Auth / DB |
| 수학 / 변환 | E2E flow |
## ❌ 안티패턴
- **Property = 1 case**: 일반 unit test 와 같음. universal 속성 찾기.
- **Generator 너무 좁음**: bug 못 찾음.
- **너무 넓음 (모든 string)**: shrinking 비싸짐. 적절히.
- **외부 부수효과 (DB)**: random 에 안 맞음. mock.
- **Random 결과를 assertion 으로**: 결정론적 property.
- **Run 횟수 너무 적음 (10)**: edge case 놓침. 100+.
- **`fc.assert` 없이 single random**: 의미 없음.
## 🤖 LLM 활용 힌트
- Round-trip / 단조성 / idempotency / 길이 보존 같은 일반 속성 찾기.
- Encoder / parser / sort / search 가 강 candidate.
- shrinking 이 디버깅 무기.
## 🔗 관련 문서
- [[Testing_Test_Pyramid]]
- [[Testing_Faker_and_Builders]]
- [[TS_Schema_Validation_Comparison]]