Beginning Test Driven Development
 
Published: 03 Mar 2009
Abstract
Many developers want to learn to use the practices of Test Driven Development. In this article, Brendan explains how to get started writing tests before writing code. He uses code snippets of tests and the implementation code to demonstrate some practices he uses when testing.
by Brendan Enrick
Feedback
Average Rating: 
Views (Total / Last 10 Days): 34765/ 53

Introduction

As a software developer, it is nearly impossible to miss all the hype which has been revolving around Test Driven Development. Many people and organizations are embracing this old, but newly popular, idea. There are testing frameworks, mocking frameworks, IoC containers, and plenty of other tools used by testers. There are a variety of different ways to go about testing, so I will begin by stating that there are alternatives to what I describe in this article. I am merely trying to explain using a style of testing similar to what I use.

In this article I will be using a few tools that you will want to have if you plan on following along. Alternatively, if you download the sample application it is bundled such that the required DLLs are included with the project.

Tools Used In the Article

·         Visual Studio 2008 - Development Environment

·         RhinoMocks - Mocking framework

·         NUnit - Testing framework

Creating Tests First

One comment that comes up often when people hear that they are supposed to write a test before they write their code is, "that it is just going to create a compiler error". Well, that is correct, but keep in mind that I said there are a variety of ways to do things. You do not have to create the tests in advance if you don't want to, but I suggest that you do. We have at least a couple of reasons why we create the tests first; it helps ensure we are testing the correct thing and it also gives the chance to come up with the desired interface to use for the code you will be testing.

Ensuring the Test is Accurate

I will tell you, that you will, at some point, write a test method which you expect to fail, but instead it passes. Plenty of reasons exist which can cause this to occur, but at the end of the day it means that your test is not accurately testing your code. All it takes to make a test pass by mistake is; swapping a less than or greater than, forgetting an assert statement, or sometimes things just do not work as you believe they do. Writing the test first allows you to confirm your theory of how everything is working.

Creating the Desired Interface

Sometimes developers will spend a great deal of time trying to figure out what interface they want to use for their code. How do you want to interact and use some piece of code? Well, one of the best ways to figure it out is to come up with a use case and attempt to use it. Tests make great use cases; they demonstrate how to use a particular class or method, and what the result of that usage is. So if we assume that the test uses our class, we are able to come up with the interface we want to use to access the class before we have written it. What does this mean? Well, it means that we now know how to design the interaction, because we've just tried to use it.

If we did not do things this way, we would be designing based on a guess of how we would want to interact with the code we are going to create. Keep this in mind when judging the value of writing tests in advance of code changes. I believe it is useful and helpful, but this is one of the points where you can easily go either way.

Creating Two Simple Tests

To start off, we want something easy just to demonstrate what types of things we want to test, and how we go about testing them. Before we dive into writing our first test, I should probably at least give a small amount of background on our example application. We are going to be working on a card game. For the purposes of this article, we will just be focusing our attention on a few classes in the game; Deck, Card, and Player.

We should always start off with the simplest thing we want to do. The first thing we need to do is come up with something our code should do. We want to then write a test which asserts that our code works as expected.

Naming your tests is extremely important. If you don't name your test well, you will not know what it is testing later and you might go off on a tangent and test something entirely different. You want to be descriptive enough to describe the problem, but you also need to be concise. For this first test, we will use the name, DeckCountShouldEqualCardCount. We will follow that test with one called, DeckShouldHaveOneLessCardAfterDrawing. Some people will read these names and think I am crazy, but let me remind you that you will have tests for every class and without names this descriptive; you will not know what is wrong when tests fail. Having a descriptive name tells you immediately what failed, because it is in the name

We know that we need to be able to draw cards and that after we draw there should be one less card in the deck, so we should start off by creating a deck with a collection of cards, keeping track of the number of initial cards. We then draw a card from the deck and assert afterwards that the number of cards in the deck has decreased by one. The following are our first test methods.

Listing 1: The Simple Tests

[Test]
public void DeckCountShouldEqualCardCount()
{
    var cards = new List<Card> {new Card(), new Card(), new Card()};
    int initialCardCount = cards.Count;
 
    var deck = new Deck(cards);
 
    Assert.AreEqual(initialCardCount, deck.Count);
}
 
[Test]
public void DeckShouldHaveOneLessCardAfterDrawing()
{
    var cards = new List<Card> {new Card(), new Card(), new Card()};
    int initialCardCount = cards.Count;
 
    var deck = new Deck(cards);
    Card card = deck.DrawCard();
 
    Assert.AreEqual(initialCardCount - 1, deck.Count);
} 

When we compile this, we get a build error because we have a little bit of implementation to do. We can assume that our 3 classes exist as empty shells for now, so the remaining work to fix the compiler errors is to create a constructor for the Deck class which takes a collection of cards, create a method called DrawCard, and create a property called Count. When we first create these we can just have them each throw a NotImplementedExcetion. Then we will no longer have the compiler errors. This is a good opportunity to run the tests and confirm that they indeed fail. This means that our code needs to be written. Having a failing test is kind of like having a task in the queue.

I am not going to cover running NUnit in this article, there are plenty of articles and tutorials that demonstrate how to run that application.

Making the Tests Pass

I do not want to spend a lot of time talking about our implementation which makes the tests pass, because it is not the focus of the article. For now I will just include a quick solution which causes the tests to pass. Keep in mind that at each step we should only write just enough to make the tests pass. We will write more tests in a moment which help us create more functionality. If we flesh out the Deck class with code that actually works, we end up with something similar to the following code.

Listing 2: The Simple Implementation

public class Deck
{
    private readonly List<Card> _cards;
 
    public Deck(List<Card> cards)
    {
        _cards = cards;
    }
 
    public int Count 
    {
        get { return _cards.Count; }
    }
 
    public Card DrawCard()
    {
        Card drawnCard = _cards[0];
        _cards.Remove(drawnCard);
        return drawnCard;
    }
}

Notice here that there are still plenty of bugs which can occur with this code. For example if the deck is empty, I am going to get an ArgumentOutOfRangeException. We need to get some more tests in place and make them pass.

Creating More Tests

As a general rule, I like to have a test for the common ways things work as well as the exceptional cases, and I like to create tests which reflect how the classes are used. We want to have at least a happy case and a sad case. The happy case is where everything runs as expected, and obviously the sad case is when it is the reverse. This can mean that we have an exception thrown or that we handle the bad occurrence. Since they are easy to create, I will start by handling an exceptional case. We need to now ask ourselves what should happen if we run out of cards. After coming up with the answer, we should create a test which states exactly that.

Listing 3: Handling Special Cases Testing

[Test]
public void EmptyDeckShouldReturnNullWhenDrawing()
{
    // Create an empty Deck
    var deck = new Deck(new List<Card>());
    Card card = deck.DrawCard();
 
    Assert.AreEqual(null, card, "A Card was returned from an empty Deck.");
}

The important thing we did here is that we defined what we expect to happen here. This obviously doesn't happen yet, we get an exception for now. We want to make sure that we get this expected result. Once we have this test in place and passing, we know what to expect anywhere that calls this method. When we execute this method, we get the exception we know we would get, which is good. The System.ArgumentOutOfRangeException lets us know that the application worked as we knew it would, because we have not written the code to make this test pass yet. While we are in the test class, we will add more tests So this article doesn't get too out of hand, we will be creating multiple tests at a time. We will also add a test which makes sure that the Card we draw is no longer in the Deck.

Listing 4: More Testing

[Test]
public void DeckShouldNotContainACardAfterDrawingIt()
{
    var deck = new Deck(new List<Card> { new Card(), new Card(), new Card() });
 
    Card card = deck.DrawCard();
 
    Assert.IsNotNull(card);
    Assert.IsFalse(deck.ContainsCard(card));
}

This test will also fail, because we haven't created a method to check if a card is in the Deck. If we add in an empty method that just throws an Exception, we will have it fail for that reason. Now we need to add in the code to make these two methods pass.

Listing 5: Implementing Passing Code

public bool ContainsCard(Card card)
{
    return _cards.Contains(card);
}

Since we already wrote most of the code, this was all that was needed to make our tests pass. Normally I write as few tests as possible at a time, so I can better focus on making sure each test is implemented correctly. This helps to make sure I am not diluting my focus when creating the interactions between objects.

Download

Conclusion

In Test Driven Development, people use the phrase, "Red, Green, Refactor". We say this because we decide what to write based on the failing test we have. Once we get the test passing, we can go in and refactor things without worrying that we are breaking this functionality. Remember that tests are there to maintain your code as well as to give you the opportunity to decide what interface you want for your code.

For more content, read Brendan Enrick's Blog.



User Comments

No comments posted yet.






Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-03-28 6:41:49 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search