tdd-workshop/TDD_REFERENCE_CARD.md
fiatcode c3355063f2 Initial commit: Complete TDD workshop materials
- Workshop documentation (WORKSHOP_PLAN, FACILITATOR_GUIDE, etc.)
- FizzBuzz kata with demo script (git history to be recreated)
- Password Validator kata with demo script and solution
- Shopping Cart kata with demo script and solution
- Setup guide and TDD reference card for participants
2026-03-10 15:32:21 +07:00

276 lines
7.8 KiB
Markdown

# TDD Reference Card
**Quick reference for Test-Driven Development practice**
---
## The RED-GREEN-REFACTOR Cycle
```
┌─────────────────────────────────────────────────┐
│ │
│ 1. RED Write a failing test │
│ ↓ (Proves the test can fail) │
│ │
│ 2. GREEN Write minimal code to pass │
│ ↓ (Make it work, don't make it perfect)│
│ │
│ 3. REFACTOR Clean up code │
│ ↓ (Remove duplication, improve names)│
│ │
│ 4. REPEAT Next failing test │
│ ↑ │
│ └───────────────────────────────────────────┘
```
---
## The Discipline
### RED - Write a Failing Test
- **Write the test first**, before any production code
- Run it and **watch it fail** (RED ❌)
- Make sure it fails **for the right reason**
- If it passes immediately, the test isn't testing anything new
### GREEN - Make It Pass
- Write the **simplest code** that makes the test pass
- Don't worry about perfection yet
- **Hardcoding is okay** if it passes the test
- Run the test and see it **pass** (GREEN ✅)
- If you're tempted to write "just one more line"—STOP and run the test
### REFACTOR - Clean Up
- Now that tests are passing, **improve the code**
- Remove duplication
- Clarify names
- Extract methods or classes
- **Run tests after every change** to ensure they stay green
- If tests go RED during refactoring, undo and try smaller steps
---
## When to Refactor
Look for these **code smells**:
| Smell | Refactoring |
|-------|-------------|
| **Duplication** | Extract method, introduce constant |
| **Unclear names** | Rename variable/method/class |
| **Long method** | Extract smaller methods |
| **Nested conditionals** | Extract methods, introduce polymorphism |
| **Magic numbers** | Replace with named constants |
| **God class** | Split responsibilities into multiple classes |
**Rule of thumb:** If you have to write a comment explaining what code does, the code needs better names.
---
## Common Test Patterns
### Arrange-Act-Assert (AAA)
```dart
test('calculates total price correctly', () {
// ARRANGE - Set up test data
final cart = ShoppingCart();
cart.addItem(CartItem(name: 'Apple', price: 1.50, quantity: 3));
// ACT - Perform the action being tested
final total = cart.subtotal();
// ASSERT - Verify the result
expect(total, equals(4.50));
});
```
### setUp() for Common Setup
```dart
void main() {
late Calculator calculator;
setUp(() {
calculator = Calculator(); // Runs before EACH test
});
test('adds two numbers', () {
expect(calculator.add(2, 3), equals(5));
});
}
```
### Test Naming
Use descriptive names that explain **what** is being tested:
**Good**: `test('rejects password shorter than 8 characters', ...)`
**Bad**: `test('test1', ...)` or `test('validation works', ...)`
---
## Dart Test Assertions - Quick Reference
```dart
// Equality
expect(actual, equals(expected));
expect(result, isTrue);
expect(result, isFalse);
// Comparison
expect(value, greaterThan(10));
expect(value, lessThan(5));
expect(value, closeTo(0.30, 0.001)); // For floating point
// Type checks
expect(object, isA<String>());
expect(value, isNull);
expect(value, isNotNull);
// Collections
expect(list, isEmpty);
expect(list, isNotEmpty);
expect(list, contains('item'));
expect(list, containsAll(['a', 'b']));
expect(list, hasLength(3));
// Exceptions
expect(() => validatePassword(''), throwsArgumentError);
expect(() => divide(1, 0), throwsA(isA<DivisionByZeroException>()));
// Strings
expect(text, startsWith('Hello'));
expect(text, endsWith('world'));
expect(text, matches(RegExp(r'\d+')));
```
---
## Quick Tips for TDD Success
### 1. Start Small
Begin with the **simplest possible test**:
- Empty input → sensible output
- Single item → correct behavior
- Then add complexity
### 2. One Test at a Time
- Uncomment or write **one test**
- Make it pass
- Then move to the next
- **Don't skip ahead!**
### 3. Baby Steps
Each cycle should take **2-5 minutes**, not 20 minutes:
- If a test takes too long, it's testing too much
- Break it into smaller tests
### 4. Run Tests Frequently
After **every code change**:
- Immediately see if something broke
- Faster feedback = faster learning
### 5. Trust the Process
The design **emerges** from the tests:
- You don't need to design everything upfront
- Simple tests lead to general solutions
- Complex scenarios often work without explicit implementation
### 6. Refactor Fearlessly
With passing tests, you can:
- Rename aggressively
- Restructure boldly
- Optimize confidently
- If tests go RED, just undo
---
## Common Mistakes to Avoid
| Mistake | Why It's Bad | Fix |
|---------|--------------|-----|
| Writing code before test | No way to know if test is valid | Always RED first |
| Writing multiple tests at once | Overwhelming, loses focus | One test at a time |
| Skipping REFACTOR | Technical debt accumulates | Clean up after each GREEN |
| Not running tests after refactor | Might break something unknowingly | Run tests constantly |
| Testing implementation details | Tests become brittle | Test behavior, not internals |
| Making tests pass by hardcoding | Doesn't prove general solution | Add another test to force generalization |
---
## What to TDD vs. What to Skip
### ✅ Great for TDD:
- **Business logic**: Calculations, validations, algorithms
- **Domain models**: Value objects, entities with rules
- **Utilities**: String parsers, formatters, converters
- **APIs**: Request/response handling, error cases
### ⚠️ Less Useful for TDD:
- **UI layouts**: Visual appearance is hard to test
- **Framework glue**: Wiring dependencies, configuration
- **Database queries**: Better tested with integration tests
- **Third-party integrations**: Better mocked or integration tested
**Rule:** If behavior matters and can be verified programmatically, use TDD.
---
## When You Get Stuck
### "I don't know what test to write next"
Ask: **"What's the simplest behavior this doesn't handle yet?"**
- Start with zero/empty cases
- Then one item
- Then two items
- Then edge cases
### "The test is too complex"
**Break it down:**
- Can you test just one part of the behavior?
- Can you introduce a helper method to simplify setup?
### "I can't make it pass without writing a lot of code"
**That's a sign:**
- The test might be too big (break into smaller tests)
- OR you're missing an intermediate test
- OR you need to refactor existing code first
### "Tests are passing but the design feels wrong"
**Good news:**
- You have tests as a safety net!
- **Refactor now** while tests are green
- Improve names, extract methods, restructure
---
## Resources for More Practice
### After This Workshop:
- **tdd-katas repo**: github.com/dhemasnurjaya/tdd-katas
- 5 complete katas with commit history
- Roman Numerals, Bowling Game, Gilded Rose, String Calculator, Mars Rover
### Books:
- *Test-Driven Development* by Kent Beck
- *Clean Code* by Robert C. Martin
- *Growing Object-Oriented Software, Guided by Tests* by Freeman & Pryce
### Online Katas:
- Kata-Log (kata-log.rocks)
- Coding Dojo (codingdojo.org)
- Exercism (exercism.org)
---
## Remember
> "Clean code that works." — Ron Jeffries
TDD is a **skill**. Like any skill:
- It feels awkward at first
- Practice makes it automatic
- The rhythm becomes second nature
**Keep practicing. Keep the cycle tight. Let the tests guide you.**