Friday, December 19, 2008

TDD with .NET - null coalescing for defaults

Yet another post in the TDD with .NET series... 

There are some fun tricks that can make code a little bit more elegant for advanced coders, though newer developers will take some adjusting.

Suppose there is a business rule that when a name isn't valid (as defined by is_valid), we should display 'unknown.'

I've created a Name class, which stores a FirstName, MiddleInitial and LastName. I have a property named FullName that returns a string.
namespace EduTest
{
    [TestFixture]
    public class NameTests
    {
        [Test]
        public void FullName_is_null_if_incomplete()
        {
            Name mick = Factory.Name;
            mick.FirstName = null;
            Assert.Equals(null, mick.FullName);
        }
    }
}
seven tests, one failure
public string FullName
{
    get
    {
        if (this.is_valid)
        {
            StringBuilder full_name = new StringBuilder();
            full_name.Append(FirstName);

            if (MiddleInitial.HasValue)
            {
                full_name.Append(' ');
                full_name.Append(MiddleInitial);
                full_name.Append('.');
            }

            full_name.Append(' ');
            full_name.Append(LastName);

            return full_name.ToString();
        }
        else
        {
            return null;
        }
    }
}
seven tests, zero failures

This method is long-ish. I would like to refactor it, but I'll hold off for now. I'm also wondering whether this logic actually belongs in this method... but without any feedback from another developer or a good idea to persue, I'm going to leave it alone until it becomes painful or I think of a better option.

Next test...
namespace EduTest
{
    [TestFixture]
    public class NameTests
    {
        [Test]
        public void ToString_is_Unknown_when_name_is_incomplete()
        {
            Name prince = new Name();
            prince.FirstName = "Prince";
            prince.LastName = null;
            Assert.Equals("Unknown", prince.ToString());
        }
    }
}

eight tests, one failure

Now I'm going to use the cool null-coalescing operator ( ?? ).
namespace Edu
{
    public class Name
    {
        public override string ToString()
        {
            return FullName ?? "Unknown";
        }
    }
}
The null coalescing operator allows you to specify what value you want used if the first value is null.

"hello" ?? "value" results in "hello"
null ?? "default" results in "default"

eight tests, zero failures

TDD with .NET - implicit conversions

Continuing the TDD in .NET series...

 namespace EduTest
{
    [TestFixture]
    public class NameTests
    {
        [Test]
        public void name_can_be_implicitly_converted_to_string()
        {
            Name mick = new Name();
            mick.FirstName = "Mick";
            mick.LastName = "Jagger";

            string micks_name = mick;
            Assert.Equals("Mick Jagger", micks_name); 
        }
    }
}

This won't compile. It complains that "Cannot implicityly convert from type 'Edu. Name' to 'string'. We can go ahead and call this a failure.

namespace Edu
{
    public class Name
    {
        public static implicit operator string(Name n)
        {
            return n.ToString();
        }
   }
}

Success. Yeah.

TDD with .NET - object validation

Using the code from the Introduction to TDD post, I now want the Name object to tell me whether or not it is valid. Let's suppose that the business rule is that names must have a first and last name.

I add this to the Name class:
        public bool is_valid
        {
            get
            {
                return true;
            }
        }
Why return true? Well, I had to return something, and I know I am going to test for things that make this false, so... meh.

So then I write this test.
        [Test]
        public void InvalidWithoutFirstName()
        {
            Name jim = new Name();
            jim.LastName = "SomeName";

            Assert.False(jim.is_valid);
        }
three tests, one failure.
Expected false, got true.
        public bool is_valid
        {
            get
            {
                return (FirstName != null); 
            }
        }

three tests, zero failures

But wait! (Someone is upset) ... what if they set it to an empty string? Or just put an exclamation point? (etc., etc.)

Well, for this example, I don't care. If there is some reason you think this is likely to happen, certainly that would be valid motivation to write a test or two.

Now things start to go a little quicker than in the last post...
        [Test]
        public void InvalidWithoutLastName()
        {
            Name jim = new Name();
            jim.FirstName = "Jim";

            Assert.False(jim.is_valid);
        }
four tests, one failure
        public bool is_valid
        {
            get
            {
                return ((FirstName != null) && (LastName != null)); 
            }
        }
four tests, zero failures

Now, because it's so obvious... 
        [Test]
        public void name_is_valid_with_expected_parts()
        {
            Name jim = new Name();
            jim.FirstName = "Jim";
            jim.LastName = "Wright"

            Assert.True(jim.is_valid);
        }
[update ... the format of this method name shows my ruby... I actually think its easier_to_read_this than ItIsToReadThis.]

One problem... this test will pass. I really want to see it fail, so...
        public bool is_valid
        {
            get
            {
                return false;
//                return ((FirstName != null) && (LastName != null)); 
            }
        }
five tests, one failure
        public bool is_valid
        {
            get
            {
                return ((FirstName != null) && (LastName != null)); 
            }
        }

five tests, zero failures

Why was I so concerned about seeing it fail? I have written tests in the past that succeeded, then I went ahead assuming that stuff worked. It turned out that the tests were written incorrectly. I was getting false positives and the code was wrong. Finding that later was a mess. This process may not be foolproof, but it's the simplest thing I can do to solve an irritating problem.

So, now we fall into the rule of three-ish... I had a high school teacher who used to say, "Once is an accident, twice is a coincidence, three times is a pattern." So, when I start to see things three-ish times in code, I look to refactor.
namespace EduTest
{
    public class Factory
    {
        public static Name Name
        {
            get
            {
                Name x = new Name();
                x.FirstName = "Jane";
                x.MiddleInitial = 'Q';
                x.LastName = "Public";
                return x;
            }
        }
    }
I'm creating a Factory class because I know this kind of thing happens all the time in testing. We often want a name that is valid, but don't care about the exact contents. In addition to the rule of three-ish, this has another benefit. If I later say that names must have a title, and that's required, then I have to go back and change everything that tests is_valid, because they all assume that only first and last name are required. Now, I can just add everything I need to the factory method, and nullify fields to make sure they are required.  Here are the changes to the test:
        [Test]
        public void InvalidWithoutFirstName()
        {
            Name jim = Factory.Name;
            jim.FirstName = null;

            Assert.False(jim.is_valid);
        }

        [Test]
        public void InvalidWithoutLastName()
        {
            Name jim = Factory.Name;
            jim.LastName = null;

            Assert.False(jim.is_valid);
        }

        [Test]
        public void name_is_valid_with_expected_parts()
        {
            Name jim = Factory.Name;
            Assert.True(jim.is_valid);
        }

Note that I don't use the factory for the tests that rely on the data in the model. It is likely to change. I only use the factory when I don't care what the data is, only that everything is properly filled out.

five tests, zero failures

There is a lot we can do to make validations cleaner, but since we only have two properties to validate, this is more than sufficient.

Introduction to Unit Testing in .NET using TDD

Test Driven Development - noun, also TDD - The step-by-step process of creating an application that is well tested by codifying expectations as tests, then implementing the solution to that test. As implied by the name, the tests drive the implementation, instead of meandering randomly through the code, changing things willy-nilly.

TDD changes the mindset of development so that it alternates between the big-picture thinking and detailed thinking in an orderly process.

Here are the steps:
  1. identify the behavior you expect
  2. write code that ensures that behavior (the test)
  3. run the code, seeing it fail 
  4. write the simplest code you can write that will correctly pass the test
  5. see it pass
  6. optionally refactor and test again
  7. repeat
EXAMPLE

In this example, I'm using csTest, though there are several good free and pay frameworks for .NET.

1. Identify the behavior you expect

I need to be able to store names. I want to be able to enter a first name, middle initial, and last name, then see the full name. 

I don't need to test C#. We assume that if we type 'class Name' that a class named Name will be available. We want to wait until there is some interaction, complexity, etc., to write a test. Here's what I'll write first:
using System;
using System.Collections.Generic;
using System.Text;

namespace Edu
{
    public class Name
    {
        public string FirstName;
        public char MiddleInitial;
        public string LastName;
    }
}
2. Write code that ensures that behavior (the test)
using System;
using System.Collections.Generic;
using System.Text;

using csUnit;
using Edu;

namespace EduTest
{
    [TestFixture]
    public class NameTests
    {

        [Test]
        public void ToStringConcatinatesFirstMiddleAndLast()
        {
            Name john = new Name();
            john.FirstName = "John";
            john.MiddleInitial = 'E';
            john.LastName = "Doe";

            Assert.Equals("John E. Doe", john.ToString());
        }
    }
}
3. Run the test, seeing it fail.

csUnit provides a Visual Studio plugin, which I use to run the test. Though this may be counter-intuitive, I'm happy to see that it fails.

one test, one failure

4. write the simplest code you can write that will correctly pass the test

Now that we have a failing test, we need to make it pass. Add this to the Name class:
        public override string ToString()
        {
            StringBuilder full_name = new StringBuilder();
            full_name.Append(FirstName);
            full_name.Append(MiddleInitial);
            full_name.Append(LastName);

            return full_name.ToString();
        }
    }
}
I run the test. Blah! Expected "John E. Doe" got "JohnEDoe". Oh, right. Ok. Change the implementation of ToString:

        public override string ToString()
        {
            StringBuilder full_name = new StringBuilder();
            full_name.Append(FirstName);
            full_name.Append(' ');
            full_name.Append(MiddleInitial);
            full_name.Append(". ");
            full_name.Append(LastName);

            return full_name.ToString();
        }
5. see it pass
one test, zero failures

Success! 

6. Optionally, refactor

We can refactor if we want. I almost always want. :)

It seems like this functionality would be more logically in a property called FullName. This makes the method more like what I've heard called an Executable Comment.
        public string FullName
        {
            get
            {
                StringBuilder full_name = new StringBuilder();
                full_name.Append(FirstName);
                full_name.Append(' ');
                full_name.Append(MiddleInitial);
                full_name.Append(". ");
                full_name.Append(LastName);

                return full_name.ToString();
            }
        }

        public override string ToString()
        {
            return FullName;
        }
7. Repeat

Great, so let's move on. Now, of course, I need to address the possibility that there might not be a middle name.
        [Test]
        public void FullNameHandlesMiddleInitialIntelligently()
        {
            Name jane = new Name();
            jane.FirstName = "Jane";
            jane.LastName = "Doe";

            Assert.Equals("Jane Doe", jane.FullName);
        }
two tests, one failure

Now, implement the solution.
        public string FullName
        {
            get
            {
                StringBuilder full_name = new StringBuilder();
                full_name.Append(FirstName);
                if (MiddleInitial.HasValue)
                {
                    full_name.Append(' ');
                    full_name.Append(MiddleInitial);
                    full_name.Append('.');
                }
                full_name.Append(' ');
                full_name.Append(LastName);

                return full_name.ToString();
            }
        }
two tests, zero failures... perfect!

Just for the sake of example, I'm going to show how unit tests allow you to avoid some kinds of FUD. I want to refactor FullName to pull out the formatting of the middle initial:
        public string FormattedMiddleInitial
        {
            get
            {
                StringBuilder initial = new StringBuilder();

                if (MiddleInitial.HasValue)
                {
                    initial.Append(' ');
                    initial.Append(MiddleInitial);
                    initial.Append('.');
                }

                return initial.ToString();
            }
        }

        public string FullName
        {
            get
            {
                StringBuilder full_name = new StringBuilder();
                full_name.Append(FirstName);
                full_name.Append(' ');
                full_name.Append(LastName);

                return full_name.ToString();
            }
        }
I skipped a step while extracting that method, but let's say I just overlooked it. I run the tests. Since I only refactored, no functionality should change.

two tests, one failure
expected "John E. Doe" but got "John Doe"

This gives me a really good idea of where to look for a problem. Ah ha! I forgot to add the call to FormattedMiddleInitial. I add in that call, and get the results I expect:

two tests, zero failures