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

View file

@ -0,0 +1,733 @@
From 71fbd5a6f30e2c7f6e5e49807cae18274bc44506 Mon Sep 17 00:00:00 2001
From: Dhemas Nurjaya <dhemasnurjaya@outlook.com>
Date: Tue, 10 Mar 2026 11:44:55 +0700
Subject: [PATCH 01/12] Initial setup: Create FizzBuzz kata structure
---
lib/fizzbuzz.dart | 1 +
pubspec.yaml | 9 +++++++++
test/fizzbuzz_test.dart | 6 ++++++
3 files changed, 16 insertions(+)
create mode 100644 lib/fizzbuzz.dart
create mode 100644 pubspec.yaml
create mode 100644 test/fizzbuzz_test.dart
diff --git a/lib/fizzbuzz.dart b/lib/fizzbuzz.dart
new file mode 100644
index 0000000..8c2215b
--- /dev/null
+++ b/lib/fizzbuzz.dart
@@ -0,0 +1 @@
+// FizzBuzz kata - empty implementation file
diff --git a/pubspec.yaml b/pubspec.yaml
new file mode 100644
index 0000000..5e6dee3
--- /dev/null
+++ b/pubspec.yaml
@@ -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
diff --git a/test/fizzbuzz_test.dart b/test/fizzbuzz_test.dart
new file mode 100644
index 0000000..efeefe0
--- /dev/null
+++ b/test/fizzbuzz_test.dart
@@ -0,0 +1,6 @@
+import 'package:test/test.dart';
+import '../lib/fizzbuzz.dart';
+
+void main() {
+ // Tests will be added step by step during the demo
+}
--
2.53.0
From 39357bebb6432b14ae45942da92de4804e747d02 Mon Sep 17 00:00:00 2001
From: Dhemas Nurjaya <dhemasnurjaya@outlook.com>
Date: Tue, 10 Mar 2026 11:45:29 +0700
Subject: [PATCH 02/12] RED: Add test for input 1 returning "1"
---
test/fizzbuzz_test.dart | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/test/fizzbuzz_test.dart b/test/fizzbuzz_test.dart
index efeefe0..27e4266 100644
--- a/test/fizzbuzz_test.dart
+++ b/test/fizzbuzz_test.dart
@@ -2,5 +2,7 @@ import 'package:test/test.dart';
import '../lib/fizzbuzz.dart';
void main() {
- // Tests will be added step by step during the demo
+ test('returns "1" for 1', () {
+ expect(fizzBuzz(1), equals('1'));
+ });
}
--
2.53.0
From 334c5b510611a36ccd7b5db02b3a7ad1e3ae0863 Mon Sep 17 00:00:00 2001
From: Dhemas Nurjaya <dhemasnurjaya@outlook.com>
Date: Tue, 10 Mar 2026 11:45:41 +0700
Subject: [PATCH 03/12] GREEN: Return "1" (hardcoded minimal solution)
---
lib/fizzbuzz.dart | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/lib/fizzbuzz.dart b/lib/fizzbuzz.dart
index 8c2215b..0178355 100644
--- a/lib/fizzbuzz.dart
+++ b/lib/fizzbuzz.dart
@@ -1 +1,5 @@
-// FizzBuzz kata - empty implementation file
+// FizzBuzz kata - TDD progression
+
+String fizzBuzz(int n) {
+ return '1';
+}
--
2.53.0
From e0ae220dea66522d1726e342820f7e2d61edaafb Mon Sep 17 00:00:00 2001
From: Dhemas Nurjaya <dhemasnurjaya@outlook.com>
Date: Tue, 10 Mar 2026 11:45:51 +0700
Subject: [PATCH 04/12] RED: Add test for input 2 returning "2"
---
test/fizzbuzz_test.dart | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/test/fizzbuzz_test.dart b/test/fizzbuzz_test.dart
index 27e4266..7b517ae 100644
--- a/test/fizzbuzz_test.dart
+++ b/test/fizzbuzz_test.dart
@@ -5,4 +5,8 @@ void main() {
test('returns "1" for 1', () {
expect(fizzBuzz(1), equals('1'));
});
+
+ test('returns "2" for 2', () {
+ expect(fizzBuzz(2), equals('2'));
+ });
}
--
2.53.0
From 4c0471c02ac4bfef56517044519c65478ecd3dbd Mon Sep 17 00:00:00 2001
From: Dhemas Nurjaya <dhemasnurjaya@outlook.com>
Date: Tue, 10 Mar 2026 11:46:03 +0700
Subject: [PATCH 05/12] GREEN: Return string representation of number
---
lib/fizzbuzz.dart | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/fizzbuzz.dart b/lib/fizzbuzz.dart
index 0178355..4da1cfa 100644
--- a/lib/fizzbuzz.dart
+++ b/lib/fizzbuzz.dart
@@ -1,5 +1,5 @@
// FizzBuzz kata - TDD progression
String fizzBuzz(int n) {
- return '1';
+ return n.toString();
}
--
2.53.0
From 919e90fd474a2cc4200c9d6d0a0235365f6b718c Mon Sep 17 00:00:00 2001
From: Dhemas Nurjaya <dhemasnurjaya@outlook.com>
Date: Tue, 10 Mar 2026 11:46:14 +0700
Subject: [PATCH 06/12] RED: Add test for input 3 returning "Fizz"
---
test/fizzbuzz_test.dart | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/test/fizzbuzz_test.dart b/test/fizzbuzz_test.dart
index 7b517ae..27b1ce6 100644
--- a/test/fizzbuzz_test.dart
+++ b/test/fizzbuzz_test.dart
@@ -9,4 +9,8 @@ void main() {
test('returns "2" for 2', () {
expect(fizzBuzz(2), equals('2'));
});
+
+ test('returns "Fizz" for 3', () {
+ expect(fizzBuzz(3), equals('Fizz'));
+ });
}
--
2.53.0
From 1db35633b102b359860a7e85361d733038e8655d Mon Sep 17 00:00:00 2001
From: Dhemas Nurjaya <dhemasnurjaya@outlook.com>
Date: Tue, 10 Mar 2026 11:46:27 +0700
Subject: [PATCH 07/12] GREEN: Return "Fizz" for numbers divisible by 3
---
lib/fizzbuzz.dart | 1 +
1 file changed, 1 insertion(+)
diff --git a/lib/fizzbuzz.dart b/lib/fizzbuzz.dart
index 4da1cfa..21150ec 100644
--- a/lib/fizzbuzz.dart
+++ b/lib/fizzbuzz.dart
@@ -1,5 +1,6 @@
// FizzBuzz kata - TDD progression
String fizzBuzz(int n) {
+ if (n % 3 == 0) return 'Fizz';
return n.toString();
}
--
2.53.0
From 08cc354e1f1798f062e9c947b7d64637870d8964 Mon Sep 17 00:00:00 2001
From: Dhemas Nurjaya <dhemasnurjaya@outlook.com>
Date: Tue, 10 Mar 2026 11:46:38 +0700
Subject: [PATCH 08/12] RED: Add test for input 5 returning "Buzz"
---
test/fizzbuzz_test.dart | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/test/fizzbuzz_test.dart b/test/fizzbuzz_test.dart
index 27b1ce6..46f279a 100644
--- a/test/fizzbuzz_test.dart
+++ b/test/fizzbuzz_test.dart
@@ -13,4 +13,8 @@ void main() {
test('returns "Fizz" for 3', () {
expect(fizzBuzz(3), equals('Fizz'));
});
+
+ test('returns "Buzz" for 5', () {
+ expect(fizzBuzz(5), equals('Buzz'));
+ });
}
--
2.53.0
From 74d6e23f3faa7c7138178ce00c784c51e97677e4 Mon Sep 17 00:00:00 2001
From: Dhemas Nurjaya <dhemasnurjaya@outlook.com>
Date: Tue, 10 Mar 2026 11:46:52 +0700
Subject: [PATCH 09/12] GREEN: Return "Buzz" for numbers divisible by 5
---
lib/fizzbuzz.dart | 1 +
1 file changed, 1 insertion(+)
diff --git a/lib/fizzbuzz.dart b/lib/fizzbuzz.dart
index 21150ec..063e7bd 100644
--- a/lib/fizzbuzz.dart
+++ b/lib/fizzbuzz.dart
@@ -2,5 +2,6 @@
String fizzBuzz(int n) {
if (n % 3 == 0) return 'Fizz';
+ if (n % 5 == 0) return 'Buzz';
return n.toString();
}
--
2.53.0
From ca23fe7f17e7d29d8e375a4a2fc1ac32bf8452c6 Mon Sep 17 00:00:00 2001
From: Dhemas Nurjaya <dhemasnurjaya@outlook.com>
Date: Tue, 10 Mar 2026 11:47:04 +0700
Subject: [PATCH 10/12] RED: Add test for input 15 returning "FizzBuzz"
---
test/fizzbuzz_test.dart | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/test/fizzbuzz_test.dart b/test/fizzbuzz_test.dart
index 46f279a..f3203e3 100644
--- a/test/fizzbuzz_test.dart
+++ b/test/fizzbuzz_test.dart
@@ -17,4 +17,8 @@ void main() {
test('returns "Buzz" for 5', () {
expect(fizzBuzz(5), equals('Buzz'));
});
+
+ test('returns "FizzBuzz" for 15', () {
+ expect(fizzBuzz(15), equals('FizzBuzz'));
+ });
}
--
2.53.0
From f15c0989635612226d40e28c8507276ecd1c976d Mon Sep 17 00:00:00 2001
From: Dhemas Nurjaya <dhemasnurjaya@outlook.com>
Date: Tue, 10 Mar 2026 11:47:17 +0700
Subject: [PATCH 11/12] GREEN: Return "FizzBuzz" for numbers divisible by both
3 and 5 (check 15 first!)
---
lib/fizzbuzz.dart | 1 +
1 file changed, 1 insertion(+)
diff --git a/lib/fizzbuzz.dart b/lib/fizzbuzz.dart
index 063e7bd..b312695 100644
--- a/lib/fizzbuzz.dart
+++ b/lib/fizzbuzz.dart
@@ -1,6 +1,7 @@
// 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();
--
2.53.0
From cd13284513f428908ec0035f0848a12c27636eaa Mon Sep 17 00:00:00 2001
From: Dhemas Nurjaya <dhemasnurjaya@outlook.com>
Date: Tue, 10 Mar 2026 11:48:30 +0700
Subject: [PATCH 12/12] Add documentation for FizzBuzz demo kata
---
DEMO_SCRIPT.md | 346 +++++++++++++++++++++++++++++++++++++++++++++++++
README.md | 56 ++++++++
2 files changed, 402 insertions(+)
create mode 100644 DEMO_SCRIPT.md
create mode 100644 README.md
diff --git a/DEMO_SCRIPT.md b/DEMO_SCRIPT.md
new file mode 100644
index 0000000..d0853e0
--- /dev/null
+++ b/DEMO_SCRIPT.md
@@ -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!"
+```
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c4ae816
--- /dev/null
+++ b/README.md
@@ -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.
--
2.53.0