4.7 KiB
4.7 KiB
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
#[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:
cargo test tests::retries_failed_operations_3_times
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:
swift test --filter RetryTests.testRetriesFailedOperations3Times
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):
npm test -- --testNamePattern="retries failed operations"
Running the test (Vitest):
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
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
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
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
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)
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
# Single test
cargo test tests::test_name
# All tests
cargo test
# With output
cargo test -- --nocapture
Swift
# Single test
swift test --filter TestClass.testName
# All tests
swift test
# With output
swift test --verbose
TypeScript (Jest)
# Single test
npm test -- --testNamePattern="test name"
# All tests
npm test
# With coverage
npm test -- --coverage
TypeScript (Vitest)
# Single test
npm test -- -t "test name"
# All tests
npm test
# With coverage
npm test -- --coverage