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.
assert_equal( "Smith", @js.last_name )
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.
@name = n
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.
"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.
@first, @last = n.split
Next, run the tests again to make sure I didn't change the behavior of any of the methods.
Loaded suite tc_nameParser
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.
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.