1. Computing

Test Driven Development in Ruby--Refactoring in Test Driven Development

Refactoring

By

Refactoring in Test Driven Development

In the first articles in this series about Test Driven Development, you've learned what TDD is, how to write code using it, how to make test pass and how to add more features. You've written tests to assure that the NameParser class is defined. You've written tests to make sure that that class returns a full name and intializes it with a string and you've written tests to ensure the class can parse a name. The first_name method works, but it's time to do a last_name method. We'll write a similar test.

def test_last_name
  assert_equal( "Smith", @js.last_name )
end

Run it and watch it fail (not shown for brevity). I implement the last_name method just as I've implemented the first_name method.

class NameParser
  def initialize(n)
    @name = n
  end

  def full_name
    @name
  end

  def first_name
    @name.split(/ /)[0]
  end

  <b>def last_name
    @name.split(/ /)[1]
  end</b>
end

I run the tests and make sure they pass (not shown for brevity). It does, and this step is finished. Or is it?

In looking at my NameParser code, I see something I don't like. I'm parsing twice, once in first name and once in last name. I don't like this at all, as I'm repeating myself. It's time for a refactor.

What's Refactoring?

"Refactoring" your code means to make some change that affects many portions of the code. In this case, it effects the entire class. I'm more or less rewriting the entire class at this point. But this really illustrates the usefulness of TDD. In refactoring, I've tainted much of the tested code I wrote before. However, the tests will tell me if my refactoring has changed their behavior or not and if it has, where the problems are.

Here's the refactored class. It fixes the problem, and doesn't repeat at all. Notice that every single method has changed.

class NameParser
  def initialize(n)
    @first, @last = n.split
  end

  def full_name
    "#{@first} #{@last}"
  end

  def first_name
    @first
  end

  def last_name
    @last
  end
end

Next, run the tests again to make sure I didn't change the behavior of any of the methods.

>ruby tc_nameParser.rb
Loaded suite tc_nameParser
Started
....
Finished in 0.015 seconds.

4 tests, 5 assertions, 0 failures, 0 errors

Everything's good. I've refactored my code to fix a problem and none of the tests have failed.

Continuing Development

Development continues in this fashion until the entire class is done. By "done," I mean it satisfies all tests. If all that's needed is a class to parse names like "John Smith", then this class is already done. If code to parse names like "John C. Smith" with middle initials is needed, then a test for that should be written and the process continued. But what's important to note here is what wasn't done.

Since the tests are dictated by what's needed, nothing that isn't needed is written. If it's needed later, tests will be written and the features will be implemented. We didn't sit down and say "OK, let's do this right and make sure it can parse names like Mr. John C. Smith III Esq." Without close guidance by the requirements of the application (the program that uses thisNameParser class) and the tests those requirements generate, it's very easy to get carried away like that.

  1. About.com
  2. Computing
  3. Ruby
  4. Advanced Ruby
  5. Test-Driven Development
  6. Test Driven Development in Ruby--Refactoring in Test Driven Development

©2014 About.com. All rights reserved.