Novice TDD: Race to Green

'Red, Green, Refactor': that's a helpful mantra for writing your code test-first. But how long should you spend on each stage, and how can you effectively separate each stage from the other? The answer: Make it Work, then Make it Good.

Race to Green

Race to Green. Worry about design later.

When you're starting out with Test-Driven Development, it's hard. Why? Because TDD demands you do two things in an unpleasant order: Design and Implement. These are two pretty different mindsets, and messing them together can get you into tangles.

To start out, I advise that you strongly separate the two. When you're writing your first test, think Design. How do I want this to work? What should my interface look like?

For example: let's say we want to write a simple program that allows Superheroes to fight each other. We can worry about the specifics later. For now, let's start from Designing an Interface to your program:

  
  # Make a couple of superheroes
  batman = Superhero.new('Batman', 2)
  superman = Superhero.new('Superman', 5)

  # Make them fight each other
  batman.fight(superman) # => returns superman
  superman.fight(batman) # => returns superman
  

Now I've defined my interface, I can write a test that simulates this. Here, I can write a Unit Test that describes the scenario, because the scenario only involves one class (Superhero).

Once I have a failing test - failing because I haven't implemented the #fight method yet, say – I need to move to the next stage: Race to Green. Here's the principle:

When Racing to Green, forget about principles of good design: Single Responsibility, Laws of Demeter, and so on. Just make it work, any way you can.

Here's me Racing to Green here:

  
  class Superhero
    attr_reader :power

    def initialize(name, power)
      # Only need power here to go green
      @power = power
    end

    def fight(other)
      return self if power > other.power
      other
    end
  end
  

Refactor

Now that I've gone green in the fastest, simplest way I can, I can think about design.

The best bit about Racing to Green? Now I can mess with my code however I like, trying out new design approaches and learning how well they work: all without worrying about functionality in my program.

My tests will tell me if I break something: they always have my back.

You should be running your tests every ten to fifteen seconds, minimum

That might seem like a lot, but running specs is like a tic for me. Even if I'm sitting thinking about my code, not writing anything, I run them every few seconds. It's reassuring to know my program works, and it frees me up to think about Design and Design only. Run your tests a lot: eventually you'll feel creeped out if you don't run them. That's a good habit.

Fiddling about a bit, let's worry about design (I've annotated my thinking first, then given the solution later):

  
  class Superhero
    # I don't like how this is open to everyone
    # who needs to read this? Can I/How can I
    # ensure only they are allowed to?
    attr_reader :power

    def initialize(name, power)
      # Not using name. Do I need to change my
      # interface? What is the point of name
      # right now?
      @power = power
    end

    def fight(other)
      # Is there any way I can kill this 'if'
      # statement? If statements kinda suck
      # because they introduce hard-to-follow
      # control flow branches
      return self if power > other.power
      other
    end
  end
  

Here's my solution to these design problems. First, let's change the interface to get rid of name:

  
  # Make a couple of superheroes
  # Their power is the only thing
  # relevant to my program now
  batman = Superhero.new(2)
  superman = Superhero.new(5)

  # Make them fight each other
  # No changes needed here
  batman.fight(superman) # => returns superman
  superman.fight(batman) # => returns superman
  

Now let's update the class to solve those design problems:

  
  class Superhero
    def initialize(power)
      @power = power
    end

    def fight(other)
      [self, other].max_by(&:power)
    end

    protected
    attr_reader :power
  end
  

Nice! I was freed up to Google for a better solution to return a max-powered object from an array, and better-encapsulated my code. My refactor is complete: now I can write another red test, race to green, and separate my design thinking from my problem-solving.