TDD in the eyes of a simpleminded: Part 1 – The NameResolver

If you didn’t read the preface yet, I strongly recommend you to invest good 5 minutes for it now; I’ll wait…


Code:


OrenTDDWorkShop.zip (37.09 KB)



the NameResolver:


This class should have a method named GetFamilyName that gets a string as parameter and return the last name of a person.


The client defines these requirements:



  • If the sent string is empty (“”), the method should return “None”.
  • The string can contain only one word – in this case, return that word.
  • The string can contain first name and last name separated by space between them. The method should return the last name (obviously).

Simple enough right ?


Let’s TDD this thing. We start with a “Create” test:


[TestFixture]
public class NameResolverTests
{
   [Test]
   public void Create()
   {
      NameResolver c = new NameResolver();
      Assert.IsNotNull(c);
   }
}


This code doesn’t compile as we don’t have NameResolver class yet. This test will check that we have an empty constructor for NameResolver and that we can actually initialize it. So let’s write the required code:


public class NameResolver
{

}


Run the test. It works. It’s time to test our GetFamilyName method. Let’s see if the first requirement works:


[Test]
public void GetFamilyName_EmptyString_ReturnTheWordNone()
{
   NameResolver c = new NameResolver();

   string actualResult = c.GetFamilyName(string.Empty);
   Assert.AreEqual(“None”, actualResult);
}


Notice the structure of the method:


MethodName_State_ExpectedResult


This will not compile as we don’t have the method GetFamilyName, so we’ll make the minimum effort to make it compile.


public class NameResolver
{
   public string GetFamilyName(string fullName)
   {
      throw new NotImplementedException(“”);
   }
}


Now it compiles. Nice. Let’s run the test. 1 passed, 1 failed(our last test).


Let’s refactor the method so it will pass, remember, minimum effort.


public string GetFamilyName(string fullName)
{
   return “None”;
}


I know, it doesn’t make sense at first. The trick here is “baby-steps”. You will add new code or refactor existing one only if you have a test that prove you *need* to do so. Our tests pass without problems so we don’t have to modify our GetFamilyName. It works as expected so far!


Before we are running along and writing another test that will fail let’s make some refactoring. We can do it with peace as we have tests to run after we change our code. In my tests you can see a line that repeat itself which is “NameResolver c = new NameResolver();”. We don’t like initializing new objects in the tests method as this initialization can get complex later on and we don’t want to change X test methods. Refactoring:


[Test]
public void Create()
{
   NameResolver c = GetNewNameResolver();
   Assert.IsNotNull(c);
}

[Test]
public void GetFamilyName_EmptyString_ReturnTheWordNone()
{
   NameResolver c = GetNewNameResolver();

   string actualResult = c.GetFamilyName(string.Empty);
   Assert.AreEqual(“None”, actualResult);
}

private NameResolver GetNewNameResolver()
{
   return new NameResolver();
}


Compile – Good. Run tests – Good. That was simple.


Let’s carry on and prove ourself that GetFamilyName is not good:


[Test]
public void GetFamilyName_OneWordOnly_ReturnThatWord()
{
   NameResolver c = GetNewNameResolver();

   string actualResult = c.GetFamilyName(“test”);
   Assert.AreEqual(“test”, actualResult);
}


Perfect, run the test – 2 passed, 1 failed (our last one)


expected: <“test”>
but was: <“None”>


This is quite obvious, we return None no matter what. Let’s fix it. remember, minimum effort.


public string GetFamilyName(string fullName)
{
   if (fullName == string.Empty)
      return “None”;

   return fullName;
}


Run tests – Good! Now that the tests were OK, we can refactor (we have the tests as our backup – to see that we didn’t introduce new bugs). You can see that the lines of GetFamlyName(“…”) and the Assert repeat itself. Let’s extract this into a private helper method and use it:


[TestFixture]
public class NameResolverTests
{
   [Test]
   public void Create()
   {
      NameResolver c = GetNewNameResolver();
      Assert.IsNotNull(c);
   }

   [Test]
   public void GetFamilyName_EmptyString_ReturnTheWordNone()
   {
      NameResolver c = GetNewNameResolver();
      VerifyGetFamilyName(c, string.Empty, “None”);
   }

   [Test]
   public void GetFamilyName_OneWordOnly_ReturnThatWord()
   {
      NameResolver c = GetNewNameResolver();
      VerifyGetFamilyName(c, “test”, “test”);
   }
   
   private
void VerifyGetFamilyName(
      NameResolver nameResolver, 
      string fullNameToCheck, 
      string expectedResult)
   {
      string actualResult = nameResolver.GetFamilyName(fullNameToCheck);
      Assert.AreEqual(expectedResult, actualResult);
   }

   private NameResolver GetNewNameResolver()
   {
      return new NameResolver();
   }
}


VerifyGetFamilyName will help us to write smaller test. We want the option to write a new test with only a couple of lines. Adding new tests should be fun.


Let’s move on.


Now we don’t have bugs anymore right? we do ?
prove it!

Here you go:


[Test]
public void GetFamilyName_TwoWords_ReturnLastWord()
{
   NameResolver c = GetNewNameResolver();
   VerifyGetFamilyName(c, “one two”, “two”);
}


Run test – 3 passed, 1 failed (our last one).
It makes sense, we don’t check for space character yet so Let’s fix it with minimum effort (remember?).


public string GetFamilyName(string fullName)
{
   if (fullName == string.Empty)
      return “None”;
   else if (fullName.IndexOf(” “) == -1) // no spaces
      return fullName;
   else
   {
      string[] words = fullName.Split(‘ ‘);
      return words[1];
   }
}


Run tests – Good !


Hold on, you think you’re done but you’re not. The client adds another requirement (things change, you know):



  • the string can contain 3 words – first name, middle name and last name; Separated by space, of course. Return the last name (obvious).

We have a bug! How do I know? Here you go:


[Test]
public void GetFamilyName_ThreeWords_ReturnLastWord()
{
   NameResolver c = GetNewNameResolver();
   VerifyGetFamilyName(c, “one two three”, “three”);
}


Run tests –  4 passed, 1 failed(our last one). Let’s fix the method so our last test will work. minimum effort.


public string GetFamilyName(string fullName)
{
   if (fullName == string.Empty)
      return “None”;
   else if (fullName.IndexOf(” “) == -1) // no spaces
      return fullName;
   else
   {
      string[] words = fullName.Split(‘ ‘);
      return words[2];
   }
}


Run tests – 4 passed, 1 failed (GetFamilyName_TwoWords_ReturnLastWord()). Oops! our last test passed but I screwed up our previous test (to two words scenario – “one two”). This is a great thing with TDD, you cover your ass with tests! Let’s refactor:


public string GetFamilyName(string fullName)
{
   if (fullName == string.Empty)
      return “None”;
   else if (fullName.IndexOf(” “) == -1) // no spaces
      return fullName;
   else
   {
      string[] words = fullName.Split(‘ ‘);
      return words[words.Length-1];
   }
}


Run tests – 5 passed, 0 failed! Good!



Conclusions:


We want to work with ~100% code coverage. That means that every piece of code in GetFamilyName method is accounted for. I don’t add new features to this method until I have a test that shows it is required. In addition, look at our GetFamilyName method, the code is so simple you want to cry with joy. Instead of thinking about all the possibilities to screw this method up at the beginning, write your “bugs” and see if the method can handle it. In addition, let’s say that the client request another feature (“I want to allow sending names in Hebrew and names in English. The GetFamilyName should be able to handle them both”. You can change whatever you need (but with minimum effort) and you’ve got the tests to see that everything is still ticking as expected. Refactoring is something you can do here with a smile on your face. You don’t have to be afraid changing the code. The well known sentence “But what if I’ll create 5 other bugs?!” is no longer valid. You’ve got the tests to tell you “You’re OK!”.

Good tests = peace of mind.

 

Oren Ellenbogen

 

5 thoughts on “TDD in the eyes of a simpleminded: Part 1 – The NameResolver

  1. Hi,
    I love your article about TDD, I think it is a good thing to test your code using automated tests, but…

    Hey, look at your code, you’ve got one method of code and about five methods for tests, while the logic is preety simple, in your first article (preface) you talked about pasha, simplicity, time consumer, isn’t that piece of code should take you about 5 minutes but it took about 15?

    don’t get me wrong, I think you are doing the right thing! but, there are times you should ask yourself some harsh questions (so further in the future you could answer those people who ask the same question, loud and clear…) such as:
    1) do we have this spare time (twice than the average time for coding?)
    2) do we realy need this test for this (simple)piece of code?
    3) or even worse, the same as pasha says, you didn’t get to work on time, why? (while you wrote some naturally tests for clear behavior.)

    one other remark:
    Most of our code is more complicated than your example, one transaction with three or more DB oprations.
    Some internal methods with important inside behavior.
    Some GUI testing.
    XML Parsers,
    Configuration dependency.

    I hope you’ll cover those too.

  2. Hold on my friend, You’re over-thinking.

    You don’t have good grasp about TDD and yet you jump to conclusions like "it takes to much time" or "you have 10 tests for one simple method". Altought I partially agree with you, I’m not going to address these issues now. Trust me that I’m thinking about them during my learning, but I’m trying to let it go for the moment. I want to be opend minded about the process; To explorer TDD to its fullest and after I’ll have good basis, I’ll start to think about the "if", "when", "how" and "how much" questions. I’ll dedicate a few posts on those tuff questions at the end of my "TDD in the eyes of a simpleminded" series.

    I’m asking you to wait a little longer, just be open minded about this one, try to see the difference in the development process.

    About advanced scenarios – coming soon…

  3. I don’t understand why you do the first refactoring. You replace

    NameResolver c = new NameResolver(); (twice)

    with

    NameResolver c = GetNewNameResolver(); (also twice)

    you replaced code duplication with duplicated code. Imho you should move the creation of the NameResolver into a setup method which is run before each test and make the NameResolver c an intance variable.

    The more interesting question that arises here: Is Object creation really duplicate code? In this simple case I’d say no. In other more complex situations maby.

    Thomas

  4. Hey Thomas, thanks for your reply.

    "I don’t understand why you do the first refactoring… you replaced code duplication with duplicated code"

    You’re asking a very good question here. I’m replace "new NameReslover();" with "GetNewNameResolver();" so I’m not "saving" any lines right ?
    Well, the purpose of this refactoring is to isolate the creation of the tested object. The reason you should do it is that the way you construct those objects can be changed (the default constructor now gets an integer for example).

    Setup method is a great solution and I use it quite often. One thing that I hate about Setup method is the tests become a little harder to maintain. You have to *remember* that some of the variables are initialized before the test begin. For this simple example, I preferred to use "method factory" to get new NameResolver as it is much easier to look at.

Comments are closed.