TDD Dive — Chapter 6

In this installment, we’re going to code up the input side. This is going to be very much like the last installment, where we did the output. The key to making this easy is to do everything in terms of TextReaders instead of specific kinds of TextReaders. This will allow us to use StringReaders in our tests, and substitute in a StreamReader later, in main. Armed with this knowledge, we can put our test code and test data in the tests themselves, as opposed to having the data spread out in little files all over the place.

So, our end goal for this part of the story is to read in an input line, parse out the Payroll command on that line, create a Payroll object, and run it. Pretty simple. Let’s start with a test list for the input reader:

  1. If no payroll input, no date is returned.
  2. If single payroll input, only one date is returned
  3. if multiple payrolls, can get all of them one at a time

I know I’ve been ignoring all error checks throughout this example. That’s because its really not the point of the exercise. If I wanted to be completey robust here, I’d stick in some stuff about making sure the input is formatted correctly, etc, but I’m really not interested in that. If this were a real system, I absolutely would be, and I’d write extra tests for those case.

So, I can’t think of any more test cases for this off the top of my head, although I might come back to this later. As usual, I want to implement the simplest test first. Here it is:

You know, I had every intention of writing that first test, but I just realized I don’t know enough to. I have no idea, in code, how this input stuff is going to be used. Proceeding in the face of this unknown seems like a recipe to build the wrong thing, so I really need to take a minute and figure out how the input side is going to be used. For right now, I’ll assume this code is going to go in main. Here is main as I’ve sketched it out. I know it is not complete yet, but this is just a thought experiement:

public static void Main(string[] args)
{
    StreamReader inputStream = new StreamReader(args[0]);
    InputReader reader = new InputReader(inputStream);

    StreamWriter outputStream = new StreamWriter(args[1]);
    CheckWriter writer = new CheckWriter(outputStream);

    EmployeeList employees = new EmployeeList();
    employees.Add(“Bill”, 144000);
    employees.Add(“Frank”, 100000);

    Payroll payroll = new Payroll(employees);

    string payDate = null;
    while((payDate = reader.GetNext()) != null)
    {
        IList payRecords = payroll.Pay(payDate);
        writer.Write(payRecords);
    }
}

As far as I can tell, that’s about how main is going to look. As of right now, the InputReader is going to produce a payDate for each line in its input. I’m pretty sure that that is going to change a bit later, but I’ll worry about that later. I don’t want to take the time now to guess about what that class’s generic behavior is going to be, as I’ll know for certain in a subsequent story.

Now I can go back to my test:

[TestFixture]
    public class InputReaderFixture
    {
        [Test]
        public void NoPayrollDateReadFromEmptyInputStream()
        {
            StringReader inputStream = new StringReader(“”);
            InputReader reader = new InputReader(inputStream);

            Assert.IsNull(reader.GetNext());
        }
    }

Very simple, and, like a similar test for the output, worked without me having to actually write any application code beyond just creating the classes and methods.

public class InputReader
{
    public InputReader(TextReader inputStream)
    {
    }

    public string GetNext()
    {
        return null;
    }
}

Next test is to include one payday command on the input and make sure I get the right date out:

[Test]
public void WillReadSinglePayrollDateFromInput()
{
    StringReader inputStream = new StringReader(“Payday,05/01/2004” + System.Environment.NewLine);
    InputReader reader = new InputReader(inputStream);

    Assert.AreEqual(“05/01/2004”, reader.GetNext());
}

At this point, I’m getting a little nervous about my idea of just returning a date string from GetNext(). I’m beginning to think I’m going to need an IList of tokens from the input line. I don’t actually need that yet, but the shape of this system is a transaction processor, where the input comes in, one transaction per line, and we act on it. To make that happen, I think we’ll need a list of tokens per line. The question now is whether I should do this now, or wait until I actually need it.

As a side point, this kind of self-examination of your code and flashes of insight are what good TDDers do all the time. It is no longer as simple as drawing up a design in Visio and implementing it. We, as TDD-programmers, have to be constantly listening to our code, re-evaluating choices we’ve made, waiting for those fundamental insights, and acting on them. That is what makes TDD more difficult than Big Design Up Front (BDUF) programming, but it is also what makes it much better.

I’m really torn about whether I should put this insight into the code now, or whether I should wait until later. The lazy programmer in me says that I would have less code to change in my tests if I made the change now. There is a very slight risk that I could get the abstraction wrong, as I don’t actually need it right now, but I don’t think it would be too tough to get it right, now. I’ll do it, as soon as I get this test working.

public class InputReader
{
    private readonly TextReader inputStream;

    public InputReader(TextReader inputStream)
    {
        this.inputStream = inputStream;
    }

    public string GetNext()
    {
        string entireLine = inputStream.ReadLine();
        if(entireLine == null) return null;

        string [] tokens = entireLine.Split(‘,’);
        return tokens[1];
    }
}

Now the test works, but the GetNext() method is a bit ugly. I have the magic character and magic number in there, and the special case stuff. Don’t like it at all. Then again, we’re about to refactor it…

This turned out to be a really simple change, and I think I’m glad I did it now. The test changed a bit, and I think I like it more now:

[Test]
public void WillReadSinglePayrollDateFromInput()
{
    StringReader inputStream = new StringReader(“Payday,05/01/2004” + System.Environment.NewLine);
    InputReader reader = new InputReader(inputStream);

    IList commandTokens = reader.GetNext();
    Assert.AreEqual(2, commandTokens.Count);
    Assert.AreEqual(“Payday”, commandTokens[0]);
    Assert.AreEqual(“05/01/2004”, commandTokens[1]);
}

Now we can assert something about the entire input line. We can confirm that it only had 2 tokens on it, and we can confirm what they were. I just like that better — it seems more complete. And, in InputReader, all I had to do now was to return the array of tokens returned from Split, rather than pull the array apart and just returning one piece of it. Very simple.

All that is left to do now is to get more than one set of tokens from the InputReader, and I think this class is done.

[Test]
public void WillReadAllSetsOfTokensInInputStream()
{
    StringReader inputStream =
        new StringReader(“Payday,05/01/2004” + System.Environment.NewLine +
                        “Payday,06/01/2004” + System.Environment.NewLine);
    InputReader reader = new InputReader(inputStream);

    IList firstCommandTokens = reader.GetNext();
    Assert.AreEqual(2, firstCommandTokens.Count);
    Assert.AreEqual(“Payday”, firstCommandTokens[0]);
    Assert.AreEqual(“05/01/2004”, firstCommandTokens[1]);

    IList secondCommandTokens = reader.GetNext();
    Assert.AreEqual(2, secondCommandTokens.Count);
    Assert.AreEqual(“Payday”, secondCommandTokens[0]);
    Assert.AreEqual(“06/01/2004”, secondCommandTokens[1]);

    IList emptyCommandTokens = reader.GetNext();
    Assert.IsNull(emptyCommandTokens);
}

Once I wrote this test, to my surprise, it worked. I guess I wasn’t thinking about it clearly enough, but the initial implementation of GetNext() was sufficient. I really expected to have to write a while look in that method, to let me get each line of tokens. Looking back at the code, I understand completely why I didn’t need to, but I expected to. Had I just sat down and written the code, I may very well have written that loop, only to discover that I didn’t need it! Not real bright of me, but sometimes that happens. My tests saved me from doing something dumb.

This is the finished InputReader class:

public class InputReader
{
   private readonly TextReader inputStream;

   public InputReader(TextReader inputStream)
   {
       this.inputStream = inputStream;
   }

   public IList GetNext()
   {
       string entireLine = inputStream.ReadLine();
       if(entireLine == null) return null;

       return entireLine.Split(‘,’);
   }
}

The main program

Since that was so easy, let’s look at main now and make sure that it works correctly. After our earlier refactoring to change to IList of tokens, here is main:

public class PayrollMain
{
    public static void Main(string[] args)
    {
        StreamReader inputStream = new StreamReader(args[0]);
        InputReader reader = new InputReader(inputStream);

        StreamWriter outputStream = new StreamWriter(args[1]);
        CheckWriter writer = new CheckWriter(outputStream);

        EmployeeList employees = new EmployeeList();
        employees.Add(“Bill”, 144000);
        employees.Add(“Frank”, 100000);

        IList tokens = reader.GetNext();
        while(tokens.Count > 0)
        {
            Payroll payroll = new Payroll(employees);
            string payDate = tokens[1] as string;

            IList payRecords = payroll.Pay(payDate);
            writer.Write(payRecords);
        }
    }
}

After running this, there were a few minor problems that I had to find through debugging, which tells me that I really should have written this code the same way I wrote the rest of it, driving it through TDD. This is also a bit complex for a main, and the while loop at least should be refactored out, but I think I can stand leaving this for now right where it is. I’m pretty sure it will get refactored out during the next story into a factory or something, as soon as we start creating other kinds of commands, but I’ll leave that for another day.

At this point, I consider this first story to be finished. It works, there are no more refactorings I can think of, we have our main built. Main worked pretty much the first time I tried it. The only two changes I had to make were to change the loop condition to checking to see if tokens was null instead of checking that the count of tokens was 0, and I had to add a TextWriter.Flush() call into CheckWriter.Write(). Let’s pretend that this last change was a bug that slipped through our testing that was found in production. In the next, and last, installment for this story, we’ll talk about how to fix bugs in TDD.

Here is the final code for main and the InputReader and its tests:

[TestFixture]
public class InputReaderFixture
{
    [Test]
    public void NoPayrollDateReadFromEmptyInputStream()
    {
        StringReader inputStream = new StringReader(“”);
        InputReader reader = new InputReader(inputStream);

        Assert.IsNull(reader.GetNext());
    }

    [Test]
    public void WillReadSinglePayrollDateFromInput()
    {
        StringReader inputStream = new StringReader(“Payday,05/01/2004” + System.Environment.NewLine);
        InputReader reader = new InputReader(inputStream);

        IList commandTokens = reader.GetNext();
        Assert.AreEqual(2, commandTokens.Count);
        Assert.AreEqual(“Payday”, commandTokens[0]);
        Assert.AreEqual(“05/01/2004”, commandTokens[1]);
    }

    [Test]
    public void WillReadAllSetsOfTokensInInputStream()
    {
        StringReader inputStream =
            new StringReader(“Payday,05/01/2004” + System.Environment.NewLine +
            “Payday,06/01/2004” + System.Environment.NewLine);
        InputReader reader = new InputReader(inputStream);

        IList firstCommandTokens = reader.GetNext();
        Assert.AreEqual(2, firstCommandTokens.Count);
        Assert.AreEqual(“Payday”, firstCommandTokens[0]);
        Assert.AreEqual(“05/01/2004”, firstCommandTokens[1]);

        IList secondCommandTokens = reader.GetNext();
        Assert.AreEqual(2, secondCommandTokens.Count);
        Assert.AreEqual(“Payday”, secondCommandTokens[0]);
        Assert.AreEqual(“06/01/2004”, secondCommandTokens[1]);

        IList emptyCommandTokens = reader.GetNext();
        Assert.IsNull(emptyCommandTokens);
    }
}

public class InputReader
{
    private readonly TextReader inputStream;

    public InputReader(TextReader inputStream)
    {
        this.inputStream = inputStream;
    }

    public IList GetNext()
    {
        string entireLine = inputStream.ReadLine();
        if(entireLine == null) return null;

        return entireLine.Split(‘,’);
    }
}

public static void Main(string[] args)
{
    if(args.Length != 2)
    {
        Console.Out.WriteLine(“usage: PayrollMain <inputFile> <outputFile>”);
        return;
    }

    StreamReader inputStream = new StreamReader(args[0]);
    InputReader reader = new InputReader(inputStream);

    StreamWriter outputStream = new StreamWriter(args[1]);
    CheckWriter writer = new CheckWriter(outputStream);

    EmployeeList employees = new EmployeeList();
    employees.Add(“Frank Furter”, 120000);
    employees.Add(“Howard Hog”, 144000);

    IList tokens = reader.GetNext();
    while(tokens != null)
    {
        Payroll payroll = new Payroll(employees);
        string payDate = tokens[1] as string;

        IList payRecords = payroll.Pay(payDate);
        writer.Write(payRecords);

        tokens = reader.GetNext();
    }
}

6 thoughts to “TDD Dive — Chapter 6”

  1. Brian,

    Thanks for this series. It’s great help to get my mind working in the morning. I look forward to every installment. Keep it going.

  2. Todd,

    There was a typo in the first installment. The output should have had two sets of checks, 100-101 and 102-103. And the input should include the 5/15 date, because no one should be paid on that date. That’s part of the test, to confirm that I only pay on the first day of the month.

    bab

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.