Loading...
Loading...
Compare original and translation side by side
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRSTNO PRODUCTION CODE WITHOUT A FAILING TEST FIRSTdigraph tdd_cycle {
rankdir=LR;
red [label="RED\nWrite failing test", shape=box, style=filled, fillcolor="#ffcccc"];
verify_red [label="Verify fails\ncorrectly", shape=diamond];
green [label="GREEN\nMinimal code", shape=box, style=filled, fillcolor="#ccffcc"];
verify_green [label="Verify passes\nAll green", shape=diamond];
refactor [label="REFACTOR\nClean up", shape=box, style=filled, fillcolor="#ccccff"];
next [label="Next", shape=ellipse];
red -> verify_red;
verify_red -> green [label="yes"];
verify_red -> red [label="wrong\nfailure"];
green -> verify_green;
verify_green -> refactor [label="yes"];
verify_green -> green [label="no"];
refactor -> verify_green [label="stay\ngreen"];
verify_green -> next;
next -> red;
}digraph tdd_cycle {
rankdir=LR;
red [label="RED\nWrite failing test", shape=box, style=filled, fillcolor="#ffcccc"];
verify_red [label="Verify fails\ncorrectly", shape=diamond];
green [label="GREEN\nMinimal code", shape=box, style=filled, fillcolor="#ccffcc"];
verify_green [label="Verify passes\nAll green", shape=diamond];
refactor [label="REFACTOR\nClean up", shape=box, style=filled, fillcolor="#ccccff"];
next [label="Next", shape=ellipse];
red -> verify_red;
verify_red -> green [label="yes"];
verify_red -> red [label="wrong\nfailure"];
green -> verify_green;
verify_green -> refactor [label="yes"];
verify_green -> green [label="no"];
refactor -> verify_green [label="stay\ngreen"];
verify_green -> next;
next -> red;
}test('retries failed operations 3 times', async () => {
let attempts = 0;
const operation = () => {
attempts++;
if (attempts < 3) throw new Error('fail');
return 'success';
};
const result = await retryOperation(operation);
expect(result).toBe('success');
expect(attempts).toBe(3);
});test('retry works', async () => {
const mock = jest.fn()
.mockRejectedValueOnce(new Error())
.mockRejectedValueOnce(new Error())
.mockResolvedValueOnce('success');
await retryOperation(mock);
expect(mock).toHaveBeenCalledTimes(3);
});Clear name, tests real behavior, focuses on one single thing
</Good>
<Bad>
```typescript
test('retry works', async () => {
const mock = jest.fn()
.mockRejectedValueOnce(new Error())
.mockRejectedValueOnce(new Error())
.mockResolvedValueOnce('success');
await retryOperation(mock);
expect(mock).toHaveBeenCalledTimes(3);
});npm test path/to/test.test.tsnpm test path/to/test.test.tsasync function retryOperation<T>(fn: () => Promise<T>): Promise<T> {
for (let i = 0; i < 3; i++) {
try {
return await fn();
} catch (e) {
if (i === 2) throw e;
}
}
throw new Error('unreachable');
}async function retryOperation<T>(
fn: () => Promise<T>,
options?: {
maxRetries?: number;
backoff?: 'linear' | 'exponential';
onRetry?: (attempt: number) => void;
}
): Promise<T> {
// YAGNI
}npm test path/to/test.test.tsnpm test path/to/test.test.ts| 品質 | 好 | 不好 |
|---|---|---|
| 最小 | 一件事。名字中的“和”?分開它。 | |
| 清除 | 名稱描述行為 | |
| 表明意圖 | 所需需要的API | 模糊了程序代碼執行 |
| Quality | Good | Bad |
|---|---|---|
| Minimal | One thing. "And" in the name? Split it. | |
| Clear | Name describes behavior | |
| Expresses Intent | Shows the desired API | Obscures how the code executes |
| 對不起 | 現實 |
|---|---|
| “太簡單了,無法測試” | 簡單的代碼中斷。測試需要 30 秒。 |
| “之後我會測試” | 測試立即通過並不能證明什麼。 |
| “達到相同目標後再進行測試” | Tests-after =“這是做什麼的?”測試優先=“這應該做什麼?” |
| “已經手動測試” | 臨時性≠系統性。沒有記錄,無法重新運行。 |
| “刪除X小時是浪費” | 沉沒成本謬誤。保留默認驗證的代碼是技術債。 |
| 「留作參考,先寫測驗」 | 你會適應它。後面就是測試了刪除就是刪除的意思。 |
| “需要先探索” | 美好的。放棄探索,從TDD開始。 |
| “努力測試=設計不清楚” | 聽聽測試。難以測試=難以使用。 |
| “TDD 會讓我放慢速度” | TDD 比調試更快。務實=測試第一。 |
| “手動測試速度更快” | 手冊不能證明邊緣情況。您將重新測試每個變更。 |
| “現有代碼沒有經過測試” | 你正在改進它。為現有代碼添加測試。 |
| Excuse | Reality |
|---|---|
| "It's too simple to test" | Simple code breaks. Writing the test takes 30 seconds. |
| "I'll test later" | Tests written later pass immediately and prove nothing. |
| "Testing after achieves the same goal" | Tests-after = "What does this do?" Test-first = "What should this do?" |
| "I already tested manually" | Ad-hoc ≠ systematic. No record, can't re-run. |
| "Deleting X hours is a waste" | Sunk cost fallacy. Keeping unvalidated code is technical debt. |
| "I'll keep this as reference and write the test first next time" | You'll adapt the test to fit it. Later becomes never. Delete means delete. |
| "I need to explore first" | Fine. Do your exploration, then start with TDD. |
| "Hard to test = unclear design" | Listen to the tests. Hard to test = hard to use. |
| "TDD slows me down" | TDD is faster than debugging. Pragmatism = test first. |
| "Manual testing is faster" | Manual can't prove edge cases. You'll have to re-test every change. |
| "Existing code has no tests" | You're improving it. Add tests for existing code. |
test('rejects empty email', async () => {
const result = await submitForm({ email: '' });
expect(result.error).toBe('Email required');
});$ npm test
FAIL: expected 'Email required', got undefinedfunction submitForm(data: FormData) {
if (!data.email?.trim()) {
return { error: 'Email required' };
}
// ...
}$ npm test
PASStest('rejects empty email', async () => {
const result = await submitForm({ email: '' });
expect(result.error).toBe('Email required');
});$ npm test
FAIL: expected 'Email required', got undefinedfunction submitForm(data: FormData) {
if (!data.email?.trim()) {
return { error: 'Email required' };
}
// ...
}$ npm test
PASS| 問題 | 解決方案 |
|---|---|
| 不知道如何測試 | 編寫想要的API。先寫斷言。詢問你的人類夥伴。 |
| 測試太複雜 | 設計太複雜了。簡化界面。 |
| 必須嘲笑一切 | 代碼耦合性太強。使用依賴注入。 |
| 測試設定巨大 | 提取助手。還是很複雜?簡化設計。 |
| Problem | Solution |
|---|---|
| Don't know how to test | Write the API you want. Write the assertions first. Consult your human partner. |
| Test is too complex | Design is too complex. Simplify the interface. |
| Have to mock everything | Code is too coupled. Use dependency injection. |
| Test setup is huge | Extract helpers. Still complex? Simplify the design. |
Production code → test exists and failed first
Otherwise → not TDDProduction code → test exists and failed first
Otherwise → not TDD