Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:06:38 +08:00
commit ed3e4c84c3
76 changed files with 20449 additions and 0 deletions

View File

@@ -0,0 +1,325 @@
# TDD Workflow Examples
This guide shows complete TDD workflows for common scenarios: bug fixes and feature additions.
## Example: Bug Fix
### Bug
Empty email is accepted when it should be rejected.
### RED Phase: Write Failing Test
**Swift:**
```swift
func testRejectsEmptyEmail() async throws {
let result = try await submitForm(FormData(email: ""))
XCTAssertEqual(result.error, "Email required")
}
```
### Verify RED: Watch It Fail
```bash
$ swift test --filter FormTests.testRejectsEmptyEmail
FAIL: XCTAssertEqual failed: ("nil") is not equal to ("Optional("Email required")")
```
**Confirms:**
- Test fails (not errors)
- Failure message shows email not being validated
- Fails because feature missing (not typos)
### GREEN Phase: Minimal Code
```swift
struct FormResult {
var error: String?
}
func submitForm(_ data: FormData) async throws -> FormResult {
if data.email.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
return FormResult(error: "Email required")
}
// ... rest of form processing
return FormResult()
}
```
### Verify GREEN: Watch It Pass
```bash
$ swift test --filter FormTests.testRejectsEmptyEmail
Test Case '-[FormTests testRejectsEmptyEmail]' passed
```
**Confirms:**
- Test passes
- Other tests still pass
- No errors or warnings
### REFACTOR: Clean Up
If multiple fields need validation:
```swift
extension FormData {
func validate() -> String? {
if email.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
return "Email required"
}
// Add other validations...
return nil
}
}
func submitForm(_ data: FormData) async throws -> FormResult {
if let error = data.validate() {
return FormResult(error: error)
}
// ... rest of form processing
return FormResult()
}
```
Run tests again to confirm still green.
---
## Example: Feature Addition
### Feature
Calculate average of non-empty list.
### RED Phase: Write Failing Test
**TypeScript:**
```typescript
describe('average', () => {
it('calculates average of non-empty list', () => {
expect(average([1, 2, 3])).toBe(2);
});
});
```
### Verify RED: Watch It Fail
```bash
$ npm test -- --testNamePattern="average"
FAIL: ReferenceError: average is not defined
```
**Confirms:**
- Function doesn't exist yet
- Test would verify the behavior when function exists
### GREEN Phase: Minimal Code
```typescript
function average(numbers: number[]): number {
const sum = numbers.reduce((acc, n) => acc + n, 0);
return sum / numbers.length;
}
```
### Verify GREEN: Watch It Pass
```bash
$ npm test -- --testNamePattern="average"
PASS: calculates average of non-empty list
```
### Add Edge Case: Empty List
**RED:**
```typescript
it('returns 0 for empty list', () => {
expect(average([])).toBe(0);
});
```
**Verify RED:**
```bash
$ npm test -- --testNamePattern="average.*empty"
FAIL: Expected: 0, Received: NaN
```
**GREEN:**
```typescript
function average(numbers: number[]): number {
if (numbers.length === 0) return 0;
const sum = numbers.reduce((acc, n) => acc + n, 0);
return sum / numbers.length;
}
```
**Verify GREEN:**
```bash
$ npm test -- --testNamePattern="average"
PASS: 2 tests passed
```
### REFACTOR: Clean Up
No duplication or unclear naming, so no refactoring needed. Move to next feature.
---
## Example: Refactoring with Tests
### Scenario
Existing function works but is hard to read. Tests exist and pass.
### Current Code
```rust
fn process(data: Vec<i32>) -> i32 {
let mut result = 0;
for item in data {
if item > 0 {
result += item * 2;
}
}
result
}
```
### Existing Tests (Already Green)
```rust
#[test]
fn processes_positive_numbers() {
assert_eq!(process(vec![1, 2, 3]), 12); // (1*2) + (2*2) + (3*2) = 12
}
#[test]
fn ignores_negative_numbers() {
assert_eq!(process(vec![1, -2, 3]), 8); // (1*2) + (3*2) = 8
}
#[test]
fn handles_empty_list() {
assert_eq!(process(vec![]), 0);
}
```
### REFACTOR: Improve Clarity
```rust
fn process(data: Vec<i32>) -> i32 {
data.iter()
.filter(|&&n| n > 0)
.map(|&n| n * 2)
.sum()
}
```
### Verify Still Green
```bash
$ cargo test
running 3 tests
test processes_positive_numbers ... ok
test ignores_negative_numbers ... ok
test handles_empty_list ... ok
test result: ok. 3 passed; 0 failed
```
**Key:** Tests prove refactoring didn't break behavior.
---
## Common Patterns
### Pattern: Adding Validation
1. **RED:** Test that invalid input is rejected
2. **Verify RED:** Confirm invalid input currently accepted
3. **GREEN:** Add validation check
4. **Verify GREEN:** Confirm validation works
5. **REFACTOR:** Extract validation if reusable
### Pattern: Adding Error Handling
1. **RED:** Test that error condition is caught
2. **Verify RED:** Confirm error currently unhandled
3. **GREEN:** Add error handling
4. **Verify GREEN:** Confirm error handled correctly
5. **REFACTOR:** Consolidate error handling if duplicated
### Pattern: Optimizing Performance
1. **Ensure tests exist and pass** (if not, add tests first)
2. **REFACTOR:** Optimize implementation
3. **Verify GREEN:** Confirm tests still pass
4. **Measure:** Confirm performance improved
**Note:** Never optimize without tests. You can't prove optimization didn't break behavior.
---
## Workflow Checklist
### For Each New Feature
- [ ] Write one failing test
- [ ] Run test, confirm it fails correctly
- [ ] Write minimal code to pass
- [ ] Run test, confirm it passes
- [ ] Run all tests, confirm no regressions
- [ ] Refactor if needed (staying green)
- [ ] Commit
### For Each Bug Fix
- [ ] Write test reproducing the bug
- [ ] Run test, confirm it fails (reproduces bug)
- [ ] Fix the bug (minimal change)
- [ ] Run test, confirm it passes (bug fixed)
- [ ] Run all tests, confirm no regressions
- [ ] Commit
### For Each Refactoring
- [ ] Confirm tests exist and pass
- [ ] Make one small refactoring change
- [ ] Run tests, confirm still green
- [ ] Repeat until refactoring complete
- [ ] Commit
---
## Anti-Patterns to Avoid
### ❌ Writing Multiple Tests Before Implementing
**Why bad:** You can't tell which test makes implementation fail. Write one, implement, repeat.
### ❌ Changing Test to Make It Pass
**Why bad:** Test should define correct behavior. If test is wrong, fix test first, then re-run RED phase.
### ❌ Adding Features Not Covered by Tests
**Why bad:** Untested code. If you need a feature, write test first.
### ❌ Skipping RED Verification
**Why bad:** Test might pass immediately, meaning it doesn't test anything new.
### ❌ Skipping GREEN Verification
**Why bad:** Test might fail for unexpected reason. Always verify expected pass.
---
## Remember
- **One test at a time:** Write test, implement, repeat
- **Watch it fail:** Proves test actually tests something
- **Watch it pass:** Proves implementation works
- **Stay green:** All tests pass before moving on
- **Refactor freely:** Tests catch breaks

View File

@@ -0,0 +1,267 @@
# TDD Language-Specific Examples
This guide provides concrete TDD examples in multiple programming languages, showing the RED-GREEN-REFACTOR cycle.
## RED Phase Examples
Write one minimal test showing what should happen.
### Rust
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn retries_failed_operations_3_times() {
let mut attempts = 0;
let operation = || -> Result<&str, &str> {
attempts += 1;
if attempts < 3 {
Err("fail")
} else {
Ok("success")
}
};
let result = retry_operation(operation);
assert_eq!(result, Ok("success"));
assert_eq!(attempts, 3);
}
}
```
**Running the test:**
```bash
cargo test tests::retries_failed_operations_3_times
```
### Swift
```swift
func testRetriesFailedOperations3Times() async throws {
var attempts = 0
let operation = { () -> Result<String, Error> in
attempts += 1
if attempts < 3 {
return .failure(RetryError.failed)
}
return .success("success")
}
let result = try await retryOperation(operation)
XCTAssertEqual(result, "success")
XCTAssertEqual(attempts, 3)
}
```
**Running the test:**
```bash
swift test --filter RetryTests.testRetriesFailedOperations3Times
```
### TypeScript
```typescript
describe('retryOperation', () => {
it('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);
});
});
```
**Running the test (Jest):**
```bash
npm test -- --testNamePattern="retries failed operations"
```
**Running the test (Vitest):**
```bash
npm test -- -t "retries failed operations"
```
### Why These Are Good
- Clear names describing the behavior
- Test real behavior, not mocks
- One thing per test
- Shows desired API
### Bad Example
```typescript
test('retry', () => {
let mockCalls = 0;
const mock = () => {
mockCalls++;
return 'success';
};
retryOperation(mock);
expect(mockCalls).toBe(1); // Tests mock, not behavior
});
```
**Why this is bad:**
- Vague name
- Tests mock behavior, not real retry logic
## GREEN Phase Examples
Write simplest code to pass the test.
### Rust
```rust
fn retry_operation<F, T, E>(mut operation: F) -> Result<T, E>
where
F: FnMut() -> Result<T, E>,
{
for i in 0..3 {
match operation() {
Ok(result) => return Ok(result),
Err(e) => {
if i == 2 {
return Err(e);
}
}
}
}
unreachable!()
}
```
### Swift
```swift
func retryOperation<T>(_ operation: () async throws -> T) async throws -> T {
var lastError: Error?
for attempt in 0..<3 {
do {
return try await operation()
} catch {
lastError = error
if attempt == 2 {
throw error
}
}
}
throw lastError!
}
```
### TypeScript
```typescript
async function retryOperation<T>(
operation: () => Promise<T>
): Promise<T> {
let lastError: Error | undefined;
for (let i = 0; i < 3; i++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
if (i === 2) {
throw error;
}
}
}
throw lastError;
}
```
### Bad Example - Over-engineered (YAGNI)
```typescript
async function retryOperation<T>(
operation: () => Promise<T>,
options: {
maxRetries?: number;
backoff?: 'linear' | 'exponential';
onRetry?: (attempt: number) => void;
shouldRetry?: (error: Error) => boolean;
} = {}
): Promise<T> {
// Don't add features the test doesn't require!
}
```
**Why this is bad:** Test only requires 3 retries. Don't add:
- Configurable retries
- Backoff strategies
- Callbacks
- Error filtering
...until a test requires them.
## Test Requirements
**Every test should:**
- Test one behavior
- Have a clear name
- Use real code (no mocks unless unavoidable)
## Verification Commands by Language
### Rust
```bash
# Single test
cargo test tests::test_name
# All tests
cargo test
# With output
cargo test -- --nocapture
```
### Swift
```bash
# Single test
swift test --filter TestClass.testName
# All tests
swift test
# With output
swift test --verbose
```
### TypeScript (Jest)
```bash
# Single test
npm test -- --testNamePattern="test name"
# All tests
npm test
# With coverage
npm test -- --coverage
```
### TypeScript (Vitest)
```bash
# Single test
npm test -- -t "test name"
# All tests
npm test
# With coverage
npm test -- --coverage
```