tdd-workshop-exercises/TDD_REFERENCE_CARD.md
fiatcode 3d94c96ed2 Initial commit: TDD Workshop exercises for participants
- Password Validator kata with starter code and tests
- Shopping Cart kata with starter code and tests
- FizzBuzz reference code (from live demo)
- Setup guide and TDD reference card
- No solutions included (participants implement themselves)
2026-03-10 15:37:58 +07:00

7.8 KiB

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)

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

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

// 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.