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
This commit is contained in:
commit
c3355063f2
26 changed files with 4725 additions and 0 deletions
276
TDD_REFERENCE_CARD.md
Normal file
276
TDD_REFERENCE_CARD.md
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
# 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.**
|
||||
Loading…
Add table
Add a link
Reference in a new issue