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:
fiatcode 2026-03-10 15:32:21 +07:00
commit c3355063f2
26 changed files with 4725 additions and 0 deletions

346
fizzbuzz/DEMO_SCRIPT.md Normal file
View file

@ -0,0 +1,346 @@
# FizzBuzz Live Demo Script
**Duration:** 15 minutes (0:100: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
View 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.

View 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
View 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

View 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'));
});
}