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
346
fizzbuzz/DEMO_SCRIPT.md
Normal file
346
fizzbuzz/DEMO_SCRIPT.md
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
# FizzBuzz Live Demo Script
|
||||
|
||||
**Duration:** 15 minutes (0:10–0:25 in workshop schedule)
|
||||
**Purpose:** Demonstrate RED-GREEN-REFACTOR cycle in real-time
|
||||
|
||||
---
|
||||
|
||||
## Setup (Before Demo Starts)
|
||||
|
||||
- [ ] Open two windows side-by-side: `lib/fizzbuzz.dart` and `test/fizzbuzz_test.dart`
|
||||
- [ ] Have terminal ready at bottom with `dart test --watch` (optional) or be ready to run `dart test` manually
|
||||
- [ ] Start with the final completed kata, then `git reset --hard <initial-commit>` to beginning
|
||||
- [ ] Have git log open in another terminal: `git log --oneline --all`
|
||||
|
||||
---
|
||||
|
||||
## Demo Flow
|
||||
|
||||
### Minutes 0-2: Introduction
|
||||
|
||||
**What to Say:**
|
||||
```
|
||||
We're going to build FizzBuzz together using TDD. Everyone knows the rules, right?
|
||||
[Pause for nods]
|
||||
|
||||
Divisible by 3 → "Fizz"
|
||||
Divisible by 5 → "Buzz"
|
||||
Divisible by both → "FizzBuzz"
|
||||
Otherwise → the number as a string
|
||||
|
||||
Here's the key: I'm NOT going to design this first. I'm going to let the tests tell me what to code.
|
||||
|
||||
Watch the rhythm:
|
||||
1. RED - Write a test, watch it fail
|
||||
2. GREEN - Write minimal code to pass
|
||||
3. REFACTOR - Clean up if needed
|
||||
|
||||
Let's start.
|
||||
```
|
||||
|
||||
**What to Do:**
|
||||
- Show the empty files
|
||||
- Run `dart test` → shows no tests or all passing (from setup)
|
||||
|
||||
---
|
||||
|
||||
### Minutes 2-4: Cycle 1 (Input 1 → "1")
|
||||
|
||||
**RED Phase:**
|
||||
|
||||
**What to Say:**
|
||||
```
|
||||
What's the simplest test I could write? Let me start with input 1 returning "1".
|
||||
```
|
||||
|
||||
**What to Do:**
|
||||
```dart
|
||||
// In test/fizzbuzz_test.dart
|
||||
test('returns "1" for 1', () {
|
||||
expect(fizzBuzz(1), equals('1'));
|
||||
});
|
||||
```
|
||||
|
||||
Run `dart test` → **FAILS** (function doesn't exist)
|
||||
|
||||
**What to Say:**
|
||||
```
|
||||
Good! It's RED. The test is failing for the right reason - the function doesn't exist yet.
|
||||
```
|
||||
|
||||
**GREEN Phase:**
|
||||
|
||||
**What to Say:**
|
||||
```
|
||||
Now, what's the minimum code to make this pass?
|
||||
[Pause, let them think]
|
||||
I could return the number... but even simpler: I'll just return "1".
|
||||
This looks silly, but watch what happens next.
|
||||
```
|
||||
|
||||
**What to Do:**
|
||||
```dart
|
||||
// In lib/fizzbuzz.dart
|
||||
String fizzBuzz(int n) {
|
||||
return '1';
|
||||
}
|
||||
```
|
||||
|
||||
Run `dart test` → **PASSES** (GREEN ✅)
|
||||
|
||||
**What to Say:**
|
||||
```
|
||||
GREEN! It passes. Now I could refactor, but there's nothing to clean up yet.
|
||||
Let's add another test.
|
||||
```
|
||||
|
||||
**Time Check:** You should be at ~4 minutes
|
||||
|
||||
---
|
||||
|
||||
### Minutes 4-6: Cycle 2 (Input 2 → "2")
|
||||
|
||||
**RED Phase:**
|
||||
|
||||
**What to Say:**
|
||||
```
|
||||
Next simplest test: input 2 should return "2".
|
||||
```
|
||||
|
||||
**What to Do:**
|
||||
```dart
|
||||
test('returns "2" for 2', () {
|
||||
expect(fizzBuzz(2), equals('2'));
|
||||
});
|
||||
```
|
||||
|
||||
Run `dart test` → **FAILS** (Expected: '2', Actual: '1')
|
||||
|
||||
**What to Say:**
|
||||
```
|
||||
RED again. Now I have duplication in my tests - both expect numbers as strings.
|
||||
That's okay. Let me make it GREEN.
|
||||
```
|
||||
|
||||
**GREEN Phase:**
|
||||
|
||||
**What to Say:**
|
||||
```
|
||||
Now the hardcoded "1" won't work. What's the simplest solution?
|
||||
[Pause]
|
||||
Return the number as a string!
|
||||
```
|
||||
|
||||
**What to Do:**
|
||||
```dart
|
||||
String fizzBuzz(int n) {
|
||||
return n.toString();
|
||||
}
|
||||
```
|
||||
|
||||
Run `dart test` → **PASSES** (both tests GREEN ✅)
|
||||
|
||||
**What to Say:**
|
||||
```
|
||||
Both tests pass! Notice how the second test forced me to generalize.
|
||||
I didn't plan that - the tests guided me.
|
||||
```
|
||||
|
||||
**Time Check:** You should be at ~6 minutes
|
||||
|
||||
---
|
||||
|
||||
### Minutes 6-9: Cycle 3 (Input 3 → "Fizz")
|
||||
|
||||
**RED Phase:**
|
||||
|
||||
**What to Say:**
|
||||
```
|
||||
Now it gets interesting. Input 3 should return "Fizz".
|
||||
```
|
||||
|
||||
**What to Do:**
|
||||
```dart
|
||||
test('returns "Fizz" for 3', () {
|
||||
expect(fizzBuzz(3), equals('Fizz'));
|
||||
});
|
||||
```
|
||||
|
||||
Run `dart test` → **FAILS** (Expected: 'Fizz', Actual: '3')
|
||||
|
||||
**GREEN Phase:**
|
||||
|
||||
**What to Say:**
|
||||
```
|
||||
Okay, now I need to check divisibility by 3.
|
||||
```
|
||||
|
||||
**What to Do:**
|
||||
```dart
|
||||
String fizzBuzz(int n) {
|
||||
if (n % 3 == 0) return 'Fizz';
|
||||
return n.toString();
|
||||
}
|
||||
```
|
||||
|
||||
Run `dart test` → **PASSES** (all 3 tests GREEN ✅)
|
||||
|
||||
**What to Say:**
|
||||
```
|
||||
All three pass! The algorithm is starting to emerge.
|
||||
```
|
||||
|
||||
**Time Check:** You should be at ~9 minutes
|
||||
|
||||
---
|
||||
|
||||
### Minutes 9-11: Cycle 4 (Input 5 → "Buzz")
|
||||
|
||||
**RED Phase:**
|
||||
|
||||
**What to Say:**
|
||||
```
|
||||
Pattern should be clear now. Input 5 returns "Buzz".
|
||||
```
|
||||
|
||||
**What to Do:**
|
||||
```dart
|
||||
test('returns "Buzz" for 5', () {
|
||||
expect(fizzBuzz(5), equals('Buzz'));
|
||||
});
|
||||
```
|
||||
|
||||
Run `dart test` → **FAILS** (Expected: 'Buzz', Actual: '5')
|
||||
|
||||
**GREEN Phase:**
|
||||
|
||||
**What to Do:**
|
||||
```dart
|
||||
String fizzBuzz(int n) {
|
||||
if (n % 3 == 0) return 'Fizz';
|
||||
if (n % 5 == 0) return 'Buzz';
|
||||
return n.toString();
|
||||
}
|
||||
```
|
||||
|
||||
Run `dart test` → **PASSES** (all 4 tests GREEN ✅)
|
||||
|
||||
**What to Say:**
|
||||
```
|
||||
Four tests passing. But we haven't handled the tricky case yet...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Minutes 11-14: Cycle 5 (Input 15 → "FizzBuzz")
|
||||
|
||||
**RED Phase:**
|
||||
|
||||
**What to Say:**
|
||||
```
|
||||
Here's where it gets fun. What should 15 return?
|
||||
[Let them answer: "FizzBuzz"]
|
||||
Right! It's divisible by both 3 AND 5.
|
||||
```
|
||||
|
||||
**What to Do:**
|
||||
```dart
|
||||
test('returns "FizzBuzz" for 15', () {
|
||||
expect(fizzBuzz(15), equals('FizzBuzz'));
|
||||
});
|
||||
```
|
||||
|
||||
Run `dart test` → **FAILS** (Expected: 'FizzBuzz', Actual: 'Fizz')
|
||||
|
||||
**What to Say:**
|
||||
```
|
||||
Uh oh! It returns "Fizz" because 15 is divisible by 3, and we check that first.
|
||||
We need to check for BOTH before checking for either individually.
|
||||
```
|
||||
|
||||
**GREEN Phase:**
|
||||
|
||||
**What to Do:**
|
||||
```dart
|
||||
String fizzBuzz(int n) {
|
||||
if (n % 15 == 0) return 'FizzBuzz'; // Or: if (n % 3 == 0 && n % 5 == 0)
|
||||
if (n % 3 == 0) return 'Fizz';
|
||||
if (n % 5 == 0) return 'Buzz';
|
||||
return n.toString();
|
||||
}
|
||||
```
|
||||
|
||||
Run `dart test` → **PASSES** (all 5 tests GREEN ✅)
|
||||
|
||||
**What to Say:**
|
||||
```
|
||||
Perfect! All tests pass. ORDER MATTERS. That's a lesson the test taught us.
|
||||
```
|
||||
|
||||
**Time Check:** You should be at ~14 minutes
|
||||
|
||||
---
|
||||
|
||||
### Minutes 14-15: Wrap Up
|
||||
|
||||
**What to Say:**
|
||||
```
|
||||
Let me show you something cool.
|
||||
[Run: git log --oneline]
|
||||
|
||||
See these commits? Each one is a RED or GREEN step. I can jump back to any point.
|
||||
|
||||
Notice what just happened:
|
||||
1. I never wrote a "design document" for FizzBuzz
|
||||
2. The tests told me what code to write
|
||||
3. The algorithm emerged naturally from simple cases to complex
|
||||
4. When I made a mistake (checking 3 before 15), the test caught it immediately
|
||||
|
||||
This is TDD. It feels weird at first, but it becomes automatic.
|
||||
|
||||
Now YOU'RE going to try it. Pick your kata and start with the first test.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tips for Smooth Delivery
|
||||
|
||||
### DO:
|
||||
- **Narrate your thinking**: "What's the simplest test?" "What's the minimum code?"
|
||||
- **Pause before answering**: Let them think for 2-3 seconds
|
||||
- **Show RED clearly**: Let the test fail, read the error message aloud
|
||||
- **Type slowly**: They're watching and learning the syntax too
|
||||
- **Run tests frequently**: After every change
|
||||
|
||||
### DON'T:
|
||||
- Rush through the cycles - the rhythm is what they're learning
|
||||
- Skip the RED step - it proves the test can fail
|
||||
- Write all tests at once - that's not TDD
|
||||
- Apologize for "simple" code - minimal solutions are the point
|
||||
- Skip explaining the 15/3/5 order issue - that's a key insight
|
||||
|
||||
### If You Make a Typo:
|
||||
**Good!** Say: "Oops, typo. See how the test catches it?" Then fix it.
|
||||
|
||||
### If Someone Asks a Question:
|
||||
Pause the demo, answer briefly, then continue. You have buffer time.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Fix |
|
||||
|-------|-----|
|
||||
| Tests won't run | Check `dart pub get` was run |
|
||||
| Can't see failure messages clearly | Increase terminal font size |
|
||||
| Running behind schedule | Skip the "wrap up" or shorten it |
|
||||
| Running ahead | Add a 6th test (e.g., input 6 → "Fizz") |
|
||||
|
||||
---
|
||||
|
||||
## After the Demo
|
||||
|
||||
Transition to hands-on:
|
||||
```
|
||||
"Alright, now you try it. Open one of the katas, uncomment the first test, and make it RED. Then make it GREEN. I'll be walking around - grab me if you get stuck. Go!"
|
||||
```
|
||||
56
fizzbuzz/README.md
Normal file
56
fizzbuzz/README.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# FizzBuzz Kata - Live Demo
|
||||
|
||||
## Purpose
|
||||
|
||||
This kata is designed for **live demonstration** during the TDD workshop introduction. It shows the RED-GREEN-REFACTOR cycle in a familiar problem that requires minimal explanation.
|
||||
|
||||
## The Rules
|
||||
|
||||
Write a function that converts numbers to strings according to these rules:
|
||||
|
||||
- Numbers divisible by 3 → `"Fizz"`
|
||||
- Numbers divisible by 5 → `"Buzz"`
|
||||
- Numbers divisible by both 3 and 5 → `"FizzBuzz"`
|
||||
- All other numbers → the number as a string (e.g., `"1"`, `"2"`)
|
||||
|
||||
## Git History as Teaching Tool
|
||||
|
||||
This kata is built with **12 commits** showing each step of the TDD cycle:
|
||||
|
||||
```bash
|
||||
git log --oneline --all
|
||||
```
|
||||
|
||||
Each commit shows either:
|
||||
- **RED**: A failing test
|
||||
- **GREEN**: Minimal code to pass
|
||||
- **REFACTOR**: Code cleanup (if needed)
|
||||
|
||||
## Using This for the Demo
|
||||
|
||||
See `DEMO_SCRIPT.md` for detailed talking points and timing guide.
|
||||
|
||||
## Running the Tests
|
||||
|
||||
```bash
|
||||
dart pub get
|
||||
dart test
|
||||
```
|
||||
|
||||
## Checking Out Individual Steps
|
||||
|
||||
To see the code at any point:
|
||||
|
||||
```bash
|
||||
git log --oneline # See all commits
|
||||
git checkout <commit-hash> # Jump to that step
|
||||
git checkout main # Return to final solution
|
||||
```
|
||||
|
||||
## The "Aha!" Moment
|
||||
|
||||
Notice how the simple tests (1, 2) led to a general solution (n.toString()), and the Fizz/Buzz tests forced the algorithm to emerge naturally. **We never designed the whole solution upfront—the tests revealed it step by step.**
|
||||
|
||||
## For Facilitators
|
||||
|
||||
This is your warm-up kata. Practice it a few times before the workshop so you can live-code it smoothly while talking through your thinking process.
|
||||
8
fizzbuzz/lib/fizzbuzz.dart
Normal file
8
fizzbuzz/lib/fizzbuzz.dart
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// FizzBuzz kata - TDD progression
|
||||
|
||||
String fizzBuzz(int n) {
|
||||
if (n % 15 == 0) return 'FizzBuzz';
|
||||
if (n % 3 == 0) return 'Fizz';
|
||||
if (n % 5 == 0) return 'Buzz';
|
||||
return n.toString();
|
||||
}
|
||||
9
fizzbuzz/pubspec.yaml
Normal file
9
fizzbuzz/pubspec.yaml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
name: fizzbuzz
|
||||
description: FizzBuzz kata for TDD workshop live demonstration
|
||||
version: 1.0.0
|
||||
|
||||
environment:
|
||||
sdk: ^3.0.0
|
||||
|
||||
dev_dependencies:
|
||||
test: ^1.24.0
|
||||
24
fizzbuzz/test/fizzbuzz_test.dart
Normal file
24
fizzbuzz/test/fizzbuzz_test.dart
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import 'package:test/test.dart';
|
||||
import '../lib/fizzbuzz.dart';
|
||||
|
||||
void main() {
|
||||
test('returns "1" for 1', () {
|
||||
expect(fizzBuzz(1), equals('1'));
|
||||
});
|
||||
|
||||
test('returns "2" for 2', () {
|
||||
expect(fizzBuzz(2), equals('2'));
|
||||
});
|
||||
|
||||
test('returns "Fizz" for 3', () {
|
||||
expect(fizzBuzz(3), equals('Fizz'));
|
||||
});
|
||||
|
||||
test('returns "Buzz" for 5', () {
|
||||
expect(fizzBuzz(5), equals('Buzz'));
|
||||
});
|
||||
|
||||
test('returns "FizzBuzz" for 15', () {
|
||||
expect(fizzBuzz(15), equals('FizzBuzz'));
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue