Chapter 4. Test-Driven Development

Test-driven development (a.k.a. test-first programming) represents an important change to the way most programmers think about writing code. The practice involves writing a test before each new piece of functionality is coded into the system. The preceding chapter hinted at this, with some of the tasks defined as tests.

The tests themselves become part of the software. They are written in the same language that the production code is being written in (in this case, C#). This is a new concept for many developers, and they find it hard to move into the mindset of writing test code before they write “production” code. This chapter emphasizes the need to develop high-quality code and guides you through exercises to reinforce the practices. To help you write the tests, you are going to use NUnit, a freely available testing framework for development with the .NET Framework. Before starting with the exercises and writing the tests, first take a step back and think about how doing so can help you with your job.

Is It Possible to Write Bug-Free Code?

This is another question I like to ask, and the response is often “no, of course not” or “not with a serious piece of software.” So my conclusion is that all these programmers who answer in this way write buggy software. With a belief that it is not possible to write bug-free code, they are setting themselves up for poor quality from day one.

As you learned in Chapter 1, one of the XP practices is playing to win. Playing to win is a mindset. If you play to win, you will believe that it is possible to write bug-free code and will aim for zero-defect solutions. This attitude gives you the edge, but attitude alone will not create bug-free code.

An interesting fact about bug-free code is that as the code is further developed, the bug count in it is less likely to increase than in code that is known to have bugs. Developers are more careful to ensure they do not introduce any errors to a solution that is bug free than they are when the solution is riddled with issues. It is what is known as “broken window syndrome.”[1]If a building is in pristine condition, it tends to remain that way; people take better care of it. When one window is broken or a bit of graffiti goes up, however, the building falls into disrepair very rapidly. The same phenomenon exists with code. When code starts to fall into disrepair (has bugs), a typical belief follows that a few more bugs will not make any difference because the code is already broken. Therefore, it is important to keep the quality of the code high all the way through the process.

Increase the Quality of Your Code

To develop zero-defect software, you need some tools and the correct way of thinking about quality. XP champions several practices to increase quality. In my opinion, one of the most powerful tools is unit testing; and by developing the tests before writing the solution code, you are “turning the volume up.” [2]Unit testing is not unique to XP, but the proliferation of agile methods, and especially XP, has raised the profile of unit testing in the past few years.

The Big Why

Our eXtreme .NET team is struggling to understand why the tests should be written first. The team turns to Eddie, the XP expert.

Introducing NUnit

Now that you understand the role of test-driven development in producing high-quality code, it is time to get started and put some code together using test-driven development. NUnit is a .NET Framework class library that can locate unit tests that have been written in your project. NUnit comes with a GUI windows application and a console-based application that both enable you to run the tests and see the results. Tests either pass or fail, as indicated by a green or red progress bar, respectively.

Creating a New Project in C# Using NUnit

NUnit (version 2.2) has been written specifically for testing .NET-based applications and is the class library used in this example. To complete this example yourself, you need Visual Studio.Net and NUnit version 2.2 installed on your machine. NUnit is available from http://www.NUnit.org and is free to use.

The aim of the exercise is not to produce a fully functional product, but to demonstrate how to write tests first. The project built through this example is a very simple calculator application, one with the functionality to add, subtract, divide, and multiply positive integers. We started this project in Chapter3 by breaking down the tasks. Now it is time to code some of those tasks, writing the tests first.

Exercise 4-1: Get NUnit Up and Running

  1. Create a new Visual C# project called MiniCalc, using the Windows Application template, as shown in Figure 4-1.

    A new C# project.

    Figure 4-1. A new C# project.

  2. In Solution Explorer, right-click References and select Add Reference.

  3. Select Browse from the dialog box and go to where you have installed NUnit (the bin directory). Select nunit.framework.dll (see Figure 4-2). If you selected the default install directory for NUnit, it will be in the C:Program FilesNUnit 2.2in folder.

    Select nunit.framework.dll.

    Figure 4-2. Select nunit.framework.dll.

  4. Click OK. The nunit.framework will display in your References list (see Figure 4-3).

    The nunit.framework in your References list.

    Figure 4-3. The nunit.framework in your References list.

  5. Add a class called CalcTests to the project (see Figure 4-4), and place the following code in the class:

    Add a class called Calctests.

    Figure 4-4. Add a class called Calctests.

    using System;
    using NUnit.Framework;
    
    namespace MiniCalc
    {
    
        [TestFixture]
        public class CalcTests
        {
            [SetUp]
            public void SetUp()
            {
                Console.Out.WriteLine("SetUp called");
            }
    
            [TearDown]
            public void TearDown()
            {
                Console.Out.WriteLine("TearDown called");
            }
    
            [Test] public void TestThatFails()
            {
                Console.Out.WriteLine("TestThatFails called");
                Assert.Fail("This test is supposed to fail!");
            }
    
            [Test] public void TestThatSucceeds()
            {
                Console.Out.WriteLine("TestThatSucceeds called");
                Assert.IsTrue(true, "This test is supposed to succeed!");
            }
        }
    }
    
  6. Build the solution.

  7. Open the NUnit GUI. Choose File > Open from the NUnit menu bar, and browse for the MiniCalc application EXE you just built. You will find it (called MiniCalc.exe) in the bindebug folder in your project directory.

  8. Click the Run button. Two tests will run, one of which will fail (see Figure 4-5).

    The NUnit GUI shows test results.

    Figure 4-5. The NUnit GUI shows test results.

Consider what we have just done. First, we created a C# project and referenced the core NUnit assembly. This contains the definitions for classes and interfaces to support the NUnit testing framework.

Then we created a class called CalcTests and decorated this with the TestFixture attribute. A fixture usually consists of methods that are somehow related, often by their functionality or resource requirements. Usually, I write a test fixture for each class I am testing. Classes with the TestFixture attribute will be the building blocks of your testing infrastructure.

You can see two other important attributed methods, SetUp and TearDown; these methods will be called directly before and after (respectively) each test method is invoked. In the SetUp method, resources that are required for each of the test methods in the fixture can be initialized. In the TearDown method, they can be cleaned up. The TearDown method will always get called after a test even if the test fails or throws an exception. This is important because it enables us to clean up and make sure the test will not affect other tests even if it fails.

Then we actually get to create some test methods, albeit rather simple ones for now. The TestThatFails and TestThatSucceeds methods are tests to get us started, and give us something to prove that we have got the framework up and running. We’ll start putting some real test code together in the next exercise. Notice these methods are decorated with the Test attribute, which identifies them to the NUnit framework as NUnit test methods.

After you have compiled this application, you can run the NUnit GUI and browse for the assembly you have just built (MiniCalc.exe). NUnit GUI then loads that assembly and lists all the types that support the TestFixture attribute. In this example, we only have the one CalcTests class. When you click the Run button, each method in the Fixture will be run, proceeded by the SetUp method and followed by the TearDown method. If you examine the output in the Console Out tab, you can see the order in which these methods are being called. You can also run tests individually by clicking them in the tree view on the left side of the window.

Exercise 4-2: Write a Test and Add Some Functionality

In this example, we start to add some functionality to the MiniCalc program, taking you through the process one step at a time so that you get the idea of how to write code, tests first.

We are going to build a very simple calculator application, with addition, subtraction, multiplication, and division functionality for positive integers. This example is fairly simple but provides some insight into how to go about building tests for your project.

  1. We create a new method in our CalcTests class to test addition. As we write this method, we believe that we should encapsulate all our calculation methods in a class, so we create a Calculator class. Notice that we haven’t actually created the class yet; we have just made some calls to use it. What we have done is defined the need for a Calculator class and required that it support the Add method. We have also defined how the Add method is expected to behave under a set of conditions, including an exception we would like to have thrown when the method is called illegally.

    This code will not compile because we have not defined the Calculator class or the exception. It is important to understand that when we write code the test-driven way, we do not define the classes or the methods we are going to need until after we have written a test that requires them. (Notice we have introduced a new type of assertion, the Assert.AreEqual method.)

    [Test] public void TestAdd()
    {
        Console.Out.WriteLine("TestAddition called");
        Calculator testCalc = new Calculator();
        //test for case of zeros'
        Assert.AreEqual(0, testCalc.Add(0, 0),
            "Adding 0 to 0 should produce 0");
        //test that param ordering isn't important
        Assert.AreEqual(1, testCalc.Add(1, 0),
            "Adding 1 to 0 should produce 1");
        Assert.AreEqual(1, testCalc.Add(0, 1),
            "Adding 0 to 1 should produce 1");
        //test for non zero case
        Assert.AreEqual(3, testCalc.Add(1, 2),
            "Adding 1 to 2 should produce 3");
        int nResult;
        try
        {
            nResult = testCalc.Add(int.MaxValue,
                int.MaxValue);
            Assert.Fail
                ("Should throw a ResultOutofRangeException");
        }
        catch (ResultOutOfRangeException)
        {
        }
    
        testCalc = null;
    }
    
  2. The next step is to build the skeleton code so that our program can compile. We need to define the Calculator class and the exception. First, we add a new class file called Calculator.cs. In this file, we then write the following code to define everything we need for the code to compile. Do not do anything other than make the code compile for now.

    Notice that I have also removed the constructor for the Calculator class. Code that does nothing is useless; so, we stick with the XP value of simplicity and just remove it.

    using System;
    
    namespace MiniCalc
    {
        public class ResultOutOfRangeException:ApplicationException
        {
        }
    
        public class Calculator
        {
            public int Add(int a, int b)
            {
                return 0;
            }
        }
    }
    
  3. In the CalcTests class, we can now remove the TestThatFails and the TestThatSucceeds methods.

  4. You can now compile this program and run it through NUnit again. It compiles, but the test does not succeed. In fact, the first assertion in our TestAdd method does succeed! It is the second assertion that fails. After a test method has a failed assertion, NUnit doesn’t carry on running any of the other code in that method. We are now in an excellent position to start writing our Add method; we need to work through the method, writing just enough code to make the test pass, and then we are done.

    Seeing the tests fail is part of the process. Sometimes you will find yourself in the fortunate position of writing a test and it passing before you have written any production code. I say “fortunate” because this teaches you that eitherthe system is already doing something you want or that your test is not testing what you think it is testing. You are fortunate in that you have learned something.

  5. First we do the simplest thing and add the parameters together and return the result. In the Add method we just created in the Calculator class, change the code as shown here.

    public int Add(int a, int b)
    {
        return a + b;
    }
    
  6. When we run this using the NUnit GUI, we see that it passes the first four assertions but fails in the try block.

    We now have two choices: We can either change the test code to expect the .NET Framework exception or write some code to make sure we are throwing our own exception. I would lean toward the latter option. We have defined how we want to see the method behave, and there is a reason we defined the method to behave that way. We have defined a contract of behavior between the class and the consumer.

    Therefore, we will change the Add method to reflect how we want it to behave.

    public int Add(int a, int b)
    {
        int result;
        result = a + b;
    
        if (result < 0)
        {
            ResultOutOfRangeException rangeEx =
                new ResultOutOfRangeException();
            throw rangeEx;
        }
        return result;
      }
    
  7. Now run the NUnit tests again, and you will see the bar go green. We have written the test and the code to pass the test. We’re done (for this function).

We have built and tested one piece of functionality, the Add method. Now we want to actually call that method from the user interface.

Exercise 4-3: Plug the Business Logic into the User Interface

  1. Add the user interface as shown in Figure 4-6 to the existing Windows Form in the project.

    Add the user interface.

    Figure 4-6. Add the user interface.

    The controls I have added are as follows:

    Two NumericUpDown controls called NumberA and NumberB

    One label with the text +

    One button called EqualsButton and the text =

    One label called Result (showing the 3 in the figure)

  2. Double-click the EqualsButton control to generate the event handler. In this click event, place the following code.

    private void EqualsButton_Click(object sender,
        System.EventArgs e)
    {
        try
        {
            Calculator calc = new Calculator();
            Result.Text =
                (calc.Add((int)NumberA.Value,
                (int)NumberB.Value).ToString());
        }
        catch (ResultOutOfRangeException)
        {
            Result.Text = "Out of Range";
        }
    }
    

Notice how none of the business logic resides inside this method; it just calls the Add method (which has been tested) and places the result on the GUI. Run it and see.

You may be asking a Why question now. Why do this? Why not just put the code in the button event handler? There are a couple of very good reasons: First, it is easier to test code that is detached from a user interface. Second, it is good programming practice to have a thin user interface layer. Suppose, for example, that we want to expose the functionality through a different kind of interface, such as the command line. It is now easy for us to reuse the business logic (addition) in a different interface. Test-driven development is helping us to write better object-oriented code. That has got to be a good thing.

Exercise 4-4: Plug the Business Logic into a Different User Interface

In this exercise, we expose our functionality through a command-line interface.

  1. Change the code in the Main method of the MiniCalc form to read as follows:

    static void Main()
    {
        int nA, nB;
        string result;
        Console.Out.WriteLine("Welcome to Mini Calc");
        Console.Out.WriteLine("Please enter the first number to add");
        nA = Convert.ToInt32(Console.In.ReadLine());
        Console.Out.WriteLine("Please enter the second number to add");
        nB = Convert.ToInt32(Console.In.ReadLine());
        try
        {
            Calculator calc = new Calculator();
            result = (calc.Add(nA, nB)).ToString();
        }
        catch (ResultOutOfRangeException)
        {
            result = "Out of Range";
        }
        Console.Out.WriteLine(result);
    }
    
  2. Change the project properties. (Right-click the project in Solution Explorer.) Set the Output Type to be a Console Application.

  3. Rebuild the project and run it from the command line. You have now reused the fully tested business logic of the Add method in a different user interface.

When developing software using tests, one of the rules is this: If you can think of a way of breaking it, write a test to prove that you can break it. Then write the code to make the test pass. At this stage, you can probably think of a few ways of breaking this code. However, the one that we explore here is the fact that we can add negative numbers in our Add method, even though the requirement was for a very simple calculator application with addition, subtraction, multiplication, and division functionality for positive integers. Let’s now write a test that breaks it.

Exercise 4-5: Write a Test to Fix a Bug

  1. In the CalcTests class, add a method called TestAddNegatives.

    [Test] public void TestAddNegatives()
    {
        Console.Out.WriteLine("TestAddNegatives called");
        Calculator testCalc = new Calculator();
        int nResult;
    
        try
        {
            nResult = testCalc.Add(-1, 0);
            Assert.Fail(
                "Should throw a NegativeParameterException");
        }
        catch (NegativeParameterException )
        {    }
    
        try
        {
            nResult = testCalc.Add(0, -1);
            Assert.Fail(
                "Should throw a NegativeParameterException");
        }
        catch (NegativeParameterException)
        {    }
    
        try
        {
            nResult =
                testCalc.Add(int.MinValue, int.MinValue);
            Assert.Fail(
                "Should throw a NegativeParameterException");
        }
        catch (NegativeParameterException)
        {    }
    
        testCalc = null;
    }
    
  2. Notice that we have added a new exception type, so the code will not compile. Remember we must have a class or method used in a test before we define it. This keeps the code as simple as possible. To make the code compile, we need to create that exception type in the Calculator class file.

    public class NegativeParameterException:ApplicationException
    {
    }
    
  3. Now the code will compile, and you can run the tests in NUnit GUI again. The test should fail, as expected. (If you are following along with these exercises, make sure you do run the tests; it is good practice.) Now we can set about writing the code to fix it. Note: It’s running the tests that is the good practice, not the tests themselves (well, the tests are part of it).

    public int Add(int a, int b)
    {
        int result;
        if (a < 0 || b < 0 )
        {
            NegativeParameterException npEx =
                new NegativeParameterException();
            throw npEx;
        }
    
        result = a + b;
    
        if (result < 0)
        {
            ResultOutOfRangeException rangeEx =
                new ResultOutOfRangeException();
            throw rangeEx;
        }
        return result;
    }
    
  4. Compile the program and run the tests again. When you see the bar go green, we’re done. We spotted a way to break it, we wrote a test to prove we could break it, and then we fixed the code so that the tests all run.

    Actually, however, we are not quite finished here yet; we now have some duplicate code in our tests. We can take out the creation of the Calculator object that is shared in both the tests and place it in the SetUp method. We can do the same with the freeing of the object in the TearDown method. After this small change, your CalcTests class should appear something like this.

    [TestFixture]
    public class CalcTests
    {
        private Calculator testCalc;
        private int nResult;
    
        [SetUp]
        public void SetUp()
        {
            Console.Out.WriteLine("SetUp called");
            testCalc = new Calculator();
        }
    
        [TearDown]
        public void TearDown()
        {
            Console.Out.WriteLine("TearDown called");
            testCalc = null;
        }
    
        [Test] public void TestAdd()
        {
            Console.Out.WriteLine("TestAddition called");
            //test for case of zeros'
            Assert.AreEqual(0, testCalc.Add(0, 0),
                "Adding 0 to 0 should produce 0");
            //test that param ordering isn't important
            Assert.AreEqual(1, testCalc.Add(1, 0),
                "Adding 1 to 0 should produce 1");
            Assert.AreEqual(1, testCalc.Add(0, 1),
                "Adding 0 to 1 should produce 1");
            //test for non zero case
            Assert.AreEqual(3, testCalc.Add(1, 2),
                "Adding 1 to 2 should produce 3");
            try
            {
                nResult = testCalc.Add(int.MaxValue,
                    int.MaxValue);
                Assert.Fail
                    ("Should throw a ResultOutofRangeException");
            }
            catch (ResultOutOfRangeException)
            {
            }
        }
    
        [Test] public void TestAddNegatives()
        {
            Console.Out.WriteLine("TestAddNegatives called");
    
            try
            {
                nResult = testCalc.Add(-1, 0);
                Assert.Fail(
                    "Should throw a NegativeParameterException");
            }
            catch (NegativeParameterException )
            {    }
    
            try
            {
                nResult = testCalc.Add(0, -1);
                Assert.Fail(
                    "Should throw a NegativeParameterException");
            }
            catch (NegativeParameterException)
            {    }
    
            try
            {
                nResult =
                    testCalc.Add(int.MinValue, int.MinValue);
                Assert.Fail(
                    "Should throw a NegativeParameterException");
            }
            catch (NegativeParameterException)
            {    }
    
        }
    }
    

Now we are ready to expand the functionality and add subtraction to the system, so the first thing to write is, you guessed it, a test for the (as yet unwritten) Subtract method.

Exercise 4-6: Write a Test and Then Add Another Function

  1. Add a new method to the CalcTests class called TestSubtract.

    [Test] public void TestSubtract()
    {
        Assert.AreEqual(0, testCalc.Subtract(0, 0),
            "Subtracting 0 from 0 should produce 0");
        Assert.AreEqual(1, testCalc.Subtract(0, 1),
                    "Subtracting 0 from 1 should produce 1");
    
        try
        {
            nResult = testCalc.Subtract(1, 0);
            Assert.Fail(
         "Subtracting 1 from 0 should throw a ResultOutofRangeException");
        }
        catch (ResultOutOfRangeException)
        {    }
    
        Assert.AreEqual(0,
            testCalc.Subtract(int.MaxValue, int.MaxValue),
            "Subtracting max value from max value should produce 0");
    
        try
        {
            nResult = testCalc.Subtract(-1, 0);
            Assert.Fail(
                "Should throw a NegativeParameterException");
        }
        catch (NegativeParameterException)
        {    }
    
        try
        {
            nResult = testCalc.Subtract(0, -1);
            Assert.Fail(
                "Should throw a NegativeParameterException");
        }
        catch (NegativeParameterException)
        {    }
    
        try
        {
            nResult = testCalc.Subtract(int.MinValue,
                int.MinValue);
            Assert.Fail(
                "Should throw a NegativeParameterException");
        }
        catch (NegativeParameterException)
        {    }
    }
    
  2. Again we have written code that will not compile; we need to add a skeleton method to the Calculator class.

    public int Subtract(int numberToSubtract,
            int subtractFrom )
    {
        return 0;
    }
    
  3. Now we can compile the code and run the tests. As expected, the bar goes red, indicating that the TestSubtract method has failed. We are in the position of knowing that if we write just enough code to make this test pass, we can declare we’re done.

    public int Subtract(int numberToSubtract,
            int subtractFrom )
    {
        int result;
        if (subtractFrom < 0 || numberToSubtract < 0 )
        {
            NegativeParameterException npEx =
                new NegativeParameterException();
            throw npEx;
        }
    
        result = subtractFrom - numberToSubtract;
        if (result < 0)
        {
            ResultOutOfRangeException rangeEx =
                new ResultOutOfRangeException();
            throw rangeEx;
        }
        return result;
    }
    
  4. Compile the program and run the tests through NUnit again. They should all pass.

If you are being observant, at this point you should notice there is some more duplicate code. The check for negative parameters occurs at the top of both the Add and Subtract methods. This can be extracted out into its own method.

Exercise 4-7: Write a Test and Then Extract the Method

  1. Before we can just change the code, we should write a test so that we can validate that the functionality we have extracted does what we want it to do. Therefore, in the CalcTests class, add a method called TestCheckForNegativeNumbers.

    [Test] public void TestCheckForNegativeNumbers()
    {
        try
        {
            testCalc.CheckForNegativeNumbers(0, 0);
        }
        catch (NegativeParameterException)
        {
            Assert.Fail("Zeros are not negative numbers");
        }
    
        try
        {
            testCalc.CheckForNegativeNumbers(1, 1);
        }
        catch (NegativeParameterException)
        {
            Assert.Fail("1's are not negative numbers");
        }
    
        try
        {
            testCalc.CheckForNegativeNumbers(
                int.MaxValue, int.MaxValue);
        }
        catch (NegativeParameterException)
        {
            Assert.Fail("Max Vals are not negative numbers");
        }
    
        try
        {
            testCalc.CheckForNegativeNumbers(-1, -1);
            Assert.Fail("-1's are negative numbers");
        }
        catch (NegativeParameterException)
        {    }
    
        try
        {
            testCalc.CheckForNegativeNumbers(
            int.MinValue, int.MinValue);
            Assert.Fail("Min Vals are negative numbers");
        }
        catch (NegativeParameterException)
        {    }
    }
    
  2. Again we have defined a function name and declared its behavior before we have written a line of code for that function. So the next thing to do is write a skeleton for that function in the Calculator class.

    public void CheckForNegativeNumbers (int a, int b)
    {    }
    
  3. Now you can compile and run the tests again. The test we just wrote should fail, as expected. So we need to fill in the CheckForNegativeNumbers method. We have the code for this already in the other two methods, so we should be able to copy and paste[3] it from the Add method into the new method.

    public void CheckForNegativeNumbers (int a, int b)
    {
        if (a < 0 || b < 0 )
        {
            NegativeParameterException npEx =
                new NegativeParameterException();
            throw npEx;
        }
    }
    
  4. Recompile the program and run the tests again; they should all pass this time. Now you can confidently replace the duplicated code in the Add and Subtract methods with a call to a method that you know has been tested. So the Add method will change from this:

    public int Add(int a, int b)
    {
        int result;
    
        if (a < 0 || b < 0 )
        {
            NegativeParameterException npEx =
                new NegativeParameterException();
            throw npEx;
        }
        .
        .
        .
    }
    

    To this:

    public int Add(int a, int b)
    {
        int result;
    
        CheckForNegativeNumbers (a, b);
        .
        .
        .
    }
    
  5. After you have replaced the code with a call to the new CheckForNegative Numbers method, compile and run the tests again to ensure that we have not broken any of our tested functionality in the Add and Subtract methods.

Exercise 4-8: Add the UI for Subtraction

  1. All that’s left to do now is hook up the UI to the Subtract method. First, make sure the project settings are changed back to build a Windows application and the main method is changed back to call the Application.Run method with a new instance of the form. To allow both addition and subtraction, you can modify the form to include radio buttons to enable the user to choose what type of calculation to do (see Figure 4-7). Name the radio buttons AddRButton and SubtractRButton.

    Add radio buttons.

    Figure 4-7. Add radio buttons.

  2. You can then change the code in the Equals button click event to accommodate this.

    private void EqualsButton_Click(object sender,
        System.EventArgs e)
    {
        try
        {
            Calculator calc = new Calculator();
            if (AddRButton.Checked)
            {
                Result.Text =
                    (calc.Add((int)NumberA.Value,
                     (int)NumberB.Value).ToString());
            }
            else
            {
                Result.Text =
                     (calc.Subtract((int)NumberB.Value,
                     (int)NumberA.Value).ToString());
            }
        }
        catch (ResultOutOfRangeException)
        {
            Result.Text = "Out of Range";
    
        }
        catch (NegativeParameterException)
        {
            Result.Text = "Negatives not allowed";
        }
    }
    
  3. Compile the program and run it. You should be able to add and subtract positive integers. Remember that we have to put the project properties back to compile this as a Windows application, and you’ll need to change the Main method back to create and run the form.

    static void Main()
    {
        Application.Run(new Form1());
    }
    

    Also note that because the minimum value of the NumericUpDown controls is zero, you cannot actually enter an invalid negative number. If you want to test this, you need to change the minimum value property of the controls.

We are not quite finished yet. The GUI layer is starting to get a bit “smelly[4]”; there is functionality that might break and that is not tested. This functionality can be taken out of the GUI layer and into the Calculator class.

Exercise 4-9: Extract Functionality Out of the UI Layer

  1. Let’s start by factoring out the decision of which type of calculation to perform. If we create a method in the Calculator to perform more than one type of calculation, we need to somehow define which calculation to do, addition or subtraction. This seems like a good case for introducing an enumerated type. We’ll start by building the test for this new method.

    [Test]
    public void TestCalculate()
    {
        Assert.AreEqual(2,
            testCalc.Calculate(1, CalcOperation.Add, 1),
                "Adding 1 to 1 failed");
        Assert.AreEqual(0,
            testCalc.Calculate(1, CalcOperation.Subtract, 1),
                "Subtracting 1 from 1 failed");
    }
    
  2. Again, this won’t compile. We need to put some skeleton code in the Calculator class file. Starting with the new enumerated type we have introduced, create this in the Calculator.s file but outside of the class.

    public enum CalcOperation
    {
        Add = 0,
        Subtract = 1,
    }
    

    Then add the framework for the Calculate function in the Calculator class.

    public int Calculate(int a, CalcOperation op, int b)
    {
        return 0;
    }
    
  3. Now we can compile and run the tests in NUnit. You can see the output provided by the Assert.AreEqual statement when it fails.

  4. Next we can move the logic for the Calculate function from the GUI layer.

    public int Calculate(int a, CalcOperation op, int b)
    {
        int nResult = 0;
        if (CalcOperation.eAdd == op)
        {
            nResult = Add(a, b);
        }
        else if (CalcOperation.eSubtract == op)
        {
            nResult = Subtract(b, a);
        }
        return nResult;
    }
    
  5. Compile and run the tests. They should pass. Now we can call this method from the UI.

    private void EqualsButton_Click(object sender,
            System.EventArgs e)
    {
        try
        {
            Calculator calc = new Calculator();
            CalcOperation op = new CalcOperation();
            if (AddRButton.Checked)
            {
                op = CalcOperation.Add;
            }
            else
            {
                op = CalcOperation.Subtract;
            }
            Result.Text = calc.Calculate((int)NumberA.Value,
                op, (int)NumberB.Value).ToString();
        }
        catch (ResultOutOfRangeException)
        {
            Result.Text = "Out of Range";
        }
        catch (NegativeParameterException)
        {
            Result.Text = "Negatives not allowed";
        }
    }
    
  6. You can simplify this further by changing the operation to be performed in the radio button handlers. To do this, you must make the local CalcOperation type a member of the Form class.

    private CalcOperation op = CalcOperation.Add;
    

    Then you must add new methods to handle the radio buttons being clicked.

    private void AddRButton_CheckedChanged(object sender,
        System.EventArgs e)
    {
        if (AddRButton.Checked)
        {
            op = CalcOperation.Add;
        }
    }
    private void SubtractRButton_CheckedChanged(object sender,
        System.EventArgs e)
    {
        if (SubtractRButton.Checked)
        {
            op = CalcOperation.Subtract;
        }
    }
    

    Finally, you can further simplify the handler method for the Equals button.

    private void EqualsButton_Click(object sender,
            System.EventArgs e)
    {
        try
        {
            Calculator calc = new Calculator();
            Result.Text = calc.Calculate((int)NumberA.Value,
                op, (int)NumberB.Value).ToString();
        }
        catch (ResultOutOfRangeException)
        {
            Result.Text = "Out of Range";
        }
        catch (NegativeParameterException)
        {
            Result.Text = "Negatives not allowed";
        }
    }
    

In this set of exercises, we have started from scratch building a project using test-driven development principles. By now, you should understand that there is a cycle to this kind of development:

  1. Decide what unit of functionality you are going to build (design); this could also be fixing a bug.

  2. Write some test code.

  3. Write skeleton code to allow a compile.

  4. Compile and run the tests. (They should fail.)

  5. Fill in the skeleton with enough code to pass the tests.

  6. Compile and run the tests. If the tests fail, go back to Step 5.

  7. Simplify the code where possible; this may involve Steps 1 through 6 again.

  8. You’re done. Take a break and start again!

Each step in the cycle is simple to do, and at the end of the cycle you should have added one unit of functionality (or fixed one bug) that has tests that pass. You can then move on with programming other units of functionality with more confidence.

How Do You Feel About TDD?

For the next exercise, we are going to build the same program twice. First, we are going to build the program without doing any tests, and then we are going to build the same program writing the tests first. We will then see how we feel about each program.

We are going to write some code to calculate some derived data from stock price data that is passed to our code. We are going to write a class library to encapsulate the calculations for other developers to use.

The library has to take a collection of prices and timestamps for a stock price and return the high and low prices for a given date. The collection is in the form of a HashTable containing DateTime objects mapping to double values to represent timestamps mapping to prices.

Exercise 4-10: Without Tests

I provide the skeleton for you, and then you can finish the exercise. This exercise should not take more than 30 minutes to complete.

  1. Start by creating a new C# project using the class library template called NoTests.

  2. Change the name of the class that is created for you from Class1.cs to CalcClass.cs.

  3. In the CalcClass.cs file, change the code to read as follows.

    using System;
    using System.Collections.Specialized;
    namespace NoTests
    {
        public class CalcClass
        {
            public void CalcHighLow(Hashtable DatePriceList,
                DateTime day, ref decimal high, ref decimal low)
            {
            }
        }
    }
    
  4. Now write the code to calculate the high and low values, but do not write any tests! Good luck.

Exercise 4-11: With Tests

We are now going to do the same project again, this time by writing tests first. I will get you started, and then you can finish the exercise yourself. This exercise should not take you more than 30 minutes to complete.

  1. Create a new C# project using the class library template called WithTests.

  2. Rename the generated class file from Class1.cs to CalcClass.cs.

  3. Change the code in the CalcClass.cs file to read as follows.

    using System;
    
    namespace CsWithTests
    {
        public class CalcClass
        {
        }
    }
    
  4. Add a reference to the Nunit.Framework.dll file.

  5. Add a new class called CalcTests.

  6. In the CalcTests class, enter the following code.

    using System;
    using System.Collections;
    using NUnit.Framework;
    namespace CsWithTests
    {
        [TestFixture]
        public class CalcTests
        {
            [Test]
            public void TestCalcHighLow()
            {
                Hashtable datePriceList;
                DateTime day;
                Decimal high;
                Decimal low;
                CalcClass calc;
    
                //calc.CalcHighLow(datePriceList, day, high, low);
            }
        }
    }
    
  7. Now you can fill in the test code yourself and then build the CalcHighLow function in the CalcClass function to make sure the tests pass. Good luck!

Confidence Test

How do you feel after completing the last two exercises? Which class library are you more confident is correct?

If you have built meaningful tests, you should feel very confident with the second library. Even if you do not believe you have put much effort into the tests in the second exercise, the very fact that you have some means of validating your code should give you some extra level of certainty.

Conclusion

This chapter has introduced you to test-driven development, which is without a doubt one of the most important practices of XP. These tests underpin many of the practices in XP, such as refactoring, short releases, continuous integration, pair programming, and collective ownership.

The tests were intended to increase your confidence in the code you are writing. If you follow the test-driven development methodology and are a member of a team of developers who are all writing tests, you will have more confidence in their code, and they will have more confidence in your code. The outcome of this is that you can now move forward more quickly and tackle problems more aggressively.

You should now see the reason this practice is called test-driven development. The tests are driving the development process. By writing the tests first, you should find your focus on the code you are writing changes substantially. Your goal now is to define how the class will be used with a test method and then to get the tests to pass. You will learn about more testing techniques later in the book.

This chapter simplified the code in several places. The next chapter explores the practice of refactoring. Refactoring provides techniques for simplifying code.

1.

Gladwell, Malcolm. The Tipping Point. Boston, Massachusetts: Back Bay Books, 2002.

2.

“Turning the volume up” means taking something that works well and making it work even better.

3.

Copy and paste is often referred to as editor inheritance by parts of the developer community.

4.

A code smell is a term used in XP to define code that has something amiss. XP teams will call code smelly when it can be improved.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.172.146