December 24, 2014
Hot Topics:

Considering Test-After Development

  • September 19, 2007
  • By Jeff Langr
  • Send Email »
  • More Articles »

In this article, I'll build a solution twice. First, I'll write the code for a "reverser" method, a simple bit of code that reverses the order of words in an input string. I'll then use the stricter technique known as test-driven development to re-build the solution.

Test-After Development

In test-after development (TAD), I use my skills as an experienced developer to write my code. I might sometimes write a test prior to development. Predominantly, however, I will first code a solution, and then look to refactor as necessary. I might code additional tests once my solution is in place, but that's my prerogative.

A first test, if I bother to code one, represents a shot at the whole ball of wax. For this example, my first test reverses a full sentence, a test case that will require the final, real logic to get it to pass. Or, at least what I think is the final required logic.

import static org.junit.Assert.*;
import org.junit.*;

public class ReverserTest {
   @Test
   public void reverse() {
      Reverser reverser = new Reverser();
      String text = "This is a short sentence";
      assertEquals("sentence short a is This",
                   reverser.reverse(text));
   }
}

The test takes me about 45 seconds to write. I have to think a bit about the reverse-order loop logic as I code a solution. My total time to get the test to pass is about 120 seconds.

public String reverse(String text) {
   StringBuilder buffer = new StringBuilder();
   String[] words = text.split(" ");
   for (int i = words.length - 1; i >= 0; i--) {
      buffer.append(words[i]);
      if (i > 0)
         buffer.append(' ');
   }
   return buffer.toString();
}

After coding, I reflect on my solution. I recognize that I should probably worry about the case where words are separated by more than a single space character. I code a solution without worrying about the tests. I'm confident that adding a call to the trim method, a small, simple change, will suffice.

public String reverse(String text) {
   StringBuilder buffer = new StringBuilder();
   String[] words = text.split(" ");
   for (int i = words.length - 1; i >= 0; i--) {
      buffer.append(words[i].trim());
      if (i > 0)
         buffer.append(' ');
   }
   return buffer.toString();
}

Ahh. Ready to ship, right? No. My experience tells me that something isn't quite right with the solution. My gut tells me to write an additional test. The test says that the string returned by the reverser method should have eliminated additional spaces.

import static org.junit.Assert.*;
import org.junit.*;

public class ReverserTest {
   private Reverser reverser;

   @Before
   public void initialize() {
      reverser = new Reverser();
   }

   @Test
   public void reverse() {
      String text = "This is a short sentence";
      assertEquals("sentence short a is This",
                   reverser.reverse(text));
   }

   @Test
   public void reverseWithExtraSpaces() {
      String text = "This is a  short sentence";
      assertEquals("sentence short a is This",
                   reverser.reverse(text));
   }
}

Time to write the test: about 30 seconds (including refactoring to a common setup method). The test fails. The actual response returns the extra space between "short" and "a."

Expected: sentence short a is This
Actual:   sentence short  a is This

Racking my brain for a few seconds, I remember that the split method takes a regular expression. I need to split on any grouping of one or more spaces, not just a single space:

public String reverse(String text) {
   StringBuilder buffer = new StringBuilder();
   String[] words = text.split("\\s+");    // <- change
   for (int i = words.length - 1; i >= 0; i--) {
      buffer.append(words[i].trim());
      if (i > 0)
         buffer.append(' ');
   }
   return buffer.toString();
}

Well, now that I think about it a bit harder, the call to trim is no longer necessary. I might not have noticed. And if I hadn't built a test, I probably wouldn't bother removing this redundancy even if I had noticed it.

Think time + solution time: 30 seconds.

Both tests pass. Ship it!

It was a good thing that I used my experience to remember to write a test for such a trivial change. Are there other tests that I need to write? Maybe I should look at the code a bit longer...





Page 1 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Rocket Fuel