| bin | ||
| lib | ||
| test | ||
| .gitignore | ||
| analysis_options.yaml | ||
| CHANGELOG.md | ||
| pubspec.lock | ||
| pubspec.yaml | ||
| README.md | ||
TDD Katas Collection
A collection of Test-Driven Development (TDD) exercises implementing classic programming katas, following Uncle Bob's Clean Code principles and Domain-Driven Design tactical patterns.
Purpose
These katas demonstrate:
- Test-Driven Development: Production code written only to pass failing tests
- Clean Code Practices: Intent-revealing names, single responsibility, domain-focused abstractions
- Domain-Driven Design: Value Objects enforce domain constraints at the type level
- The Craftsman's Way: Quality is not a trade-off for speed; it is the only way to go fast
Completed Katas
1. Roman Numerals ✅
Implementation: lib/roman_numerals.dart
Tests: test/roman_numerals_test.dart
Converts integers (1-3999) to Roman numeral notation using a table-driven greedy algorithm.
Domain Context
Roman numerals represent numbers using seven basic symbols with specific combination rules:
Symbols:
I= 1,V= 5,X= 10,L= 50,C= 100,D= 500,M= 1000
Domain Rules:
- Additive Notation: Symbols placed in descending order are summed (e.g.,
VI= 6) - Subtractive Notation: Smaller symbol before larger subtracts (e.g.,
IV= 4) - Repetition Limit: Symbols repeat maximum three times (e.g.,
III= 3, notIIII) - Valid Range: Classical Roman numerals represent 1-3999
Subtractive Pairs (Domain Constraint): Only specific pairs use subtractive notation:
IV(4),IX(9)XL(40),XC(90)CD(400),CM(900)
Key Design Decisions
Value Object Pattern: RomanNumeralInput enforces domain invariants (1-3999 range). Invalid inputs are impossible to construct.
Table-Driven Algorithm: The conversionRules table is the domain model—it directly represents Roman numeral encoding rules.
Greedy Decomposition: The algorithm mirrors how Romans actually encoded numbers: repeatedly subtract the largest applicable value.
Usage
import 'package:tdd_katas/roman_numerals.dart';
integerToRoman(1994); // Returns: 'MCMXCIV'
integerToRoman(0); // Throws: ArgumentError
2. [Next Kata Name] 🚧
Status: Not yet started
Implementation: lib/[next_kata].dart
Tests: test/[next_kata]_test.dart
Coming soon...
3. [Third Kata Name] 🚧
Status: Not yet started
Implementation: lib/[third_kata].dart
Tests: test/[third_kata]_test.dart
Coming soon...
Running Tests
# Run all tests
dart test
# Run specific test file
dart test test/roman_numerals_test.dart
# Run with coverage
dart test --coverage
Development Philosophy
The Craftsman's Standard
"Clean code that works." — Ron Jeffries
Every kata in this collection follows:
- Uncle Bob's Clean Code: Intent-revealing names, functions do one thing, no comments needed
- Kent Beck's TDD: Red-Green-Refactor discipline, tests first
- Eric Evans' DDD: Domain concepts drive the model, tactical patterns enforce boundaries
- The Boy Scout Rule: Every commit leaves the code cleaner than before
TDD Discipline Applied
- Red: Write a failing test
- Green: Write the simplest code to pass
- Refactor: Clean up duplication, improve names
- Repeat: Let the design emerge from tests
Roman Numerals: Detailed Journey
Test Strategy
Tests are organized by domain concepts, not technical structure:
Basic Symbols: Tests for the seven fundamental symbols (I, V, X, L, C, D, M)
Subtractive Notation: Tests for all six subtractive pairs, verifying the domain rule
Additive Combinations: Tests for repeated symbols and multi-symbol sequences
Complex Edge Cases: Stress tests combining multiple rules:
1994 → MCMXCIV(year notation)3999 → MMMCMXCIX(maximum valid value)444 → CDXLIV(all subtractive positions)
Constraint Validation: Boundary tests for the valid range (1-3999)
Development Timeline
- Red: Tests for 1-5 (basic additive, first subtractive case)
- Green: Minimal implementation with conditionals
- Refactor: Extract symbol mapping, clarify intent
- Red: Tests for 6-10 (reveals pattern)
- Green: Extend conditionals
- Refactor: Recognize duplication → Table-driven approach emerges
- Red: Tests for 40-1000 (remaining symbols)
- Green: Extend conversion table (algorithm unchanged)
- Red: Edge cases and constraint tests
- Green: Add
RomanNumeralInputValue Object - Refactor: Extract validation, organize tests by domain concept
Key Insights
The Algorithm Never Changed: After the table-driven refactoring, adding 40-1000 required zero logic modifications. This validates the abstraction.
Type System as Domain Enforcer: RomanNumeralInput makes invalid states unrepresentable. You cannot construct a Roman numeral for 0 or 4000—the compiler prevents it.
Tests as Living Documentation: Test names use ubiquitous language from the Roman numeral domain. A domain expert could read the test file and recognize the rules they explained.
References
- Roman Numerals Rules
- Clean Code by Robert C. Martin
- Domain-Driven Design by Eric Evans
- Test-Driven Development by Kent Beck
License
This is a learning exercise. Use freely for educational purposes.
Following The Craftsman's Way: Quality is not negotiable.