Friday, September 6, 2013

Test-Driven Development

If you're like me, you've heard of Test-Driven Development (TDD) and you're kinda interested, but you haven't put a lot of time into figuring out how to do it.  Maybe you started down the path a long time ago and just got distracted (or bored, or you saw a squirrel).  Well, I recently had good cause to figure it all out and it turns out, it's really easy to learn.

The catch with learning TDD is that (if you're like me) you have to completely change the way you code against requirements.  No big deal, right?  So let me dive in and see if I can explain it simply for you (and remember that my blog is really more about creating an online repository of my own thoughts so I can figure out what I was thinking down the road).

TDD follows a pretty basic process that can be defined as: Red, Green, Refactor.  I prefer to refer to it as Fail, Pass, Clean Up, but that's just because I like to be contrary.  I find TDD easiest when I can identify acceptance criteria of my requirements.  Now, if you do agile you might be mentally protesting (or perhaps verbally, I don't know how unstable you are) that you're not supposed to have firm requirements in agile and that is sorta true, depending on your company's interpretation and implementation of agile.  But that's beside the point.  The point is that regardless of your development methodology you must have acceptance criteria.  Without acceptance criteria how will you know when you're finished coding and how will you know whether you've met your customers' needs?  So we identify acceptance criteria.  For simplicity, let's say our acceptance criteria is "the program displays the entered name".  Once you have your acceptance criteria, you can write your test.  Wait, what?!  Yeah, you create your test based on the acceptance criteria before you write any code at all.  That's why it's called Test-Driven Development.

[TestMethod]
public void ProgramShouldDisplayEnteredName()
{
    var p = new Program();
    var input = "Engineer_Andrew";
    var output = p.DisplayName(input);
    Assert.AreEqual("Engineer_Andrew", output);
}
That test fails, and that's a good thing.  The failed test is our "Red" part.  At this point, my program compiles, but the code doesn't actually do anything.  In fact, the code just throws an exception:

internal object DisplayName(string input)
{
    throw new NotImplementedException();
}

Now that we have a failing test (and we're proud of it) we write just enough code to make it turn "Green", or pass:

internal object DisplayName(string input)
{
    return input;
}

Since this test passes, we can move on to the third step; Refactor.  This is the part where we are going to refactor our code.  If you aren't familiar with that concept, it essentially means you're going to go back and make your code cleaner and/or more efficient and/or more extensible.  It can mean a lot of things, to be honest with you.  In this case it means we're going to do some validation of our input before we return it and we're going to return a string instead of an object:

internal object DisplayName(string input)
{
    if (string.IsNullOrEmpty(input))
    {
        return "Missing Name!";
    }
 
    return input;
}

The only thing left to do is re-run the same test as before and make sure it still passes.  It is also best practice to run all of the tests in your test suite to make sure you didn't inadvertently break something else.  As long as everything passes, you've met your acceptance criteria and you can move on to the next bit of code.

This is a seriously simplified example, but even on a larger scale the method provided here still holds.  Red, Green, Refactor (Fail, Pass, Clean Up).  That's all there is to it.

No comments:

Post a Comment