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.