Test-Driven Development in .NET

Running Your Tests & Doing TDD

Now that we have covered the basics of the code, lets talk about how to run your tests. It is really quite simple. NUnit comes with two different Test Runner applications: a Windows GUI app and a console XML app. Which one you use is a matter of personal preference. To use the GUI app, just run the application and tell it where your test assembly resides. The test assembly is the class library (or executable) that contains the Test Fixtures. The app will then show you a graphical view of each class and test that is in that assembly. To run the entire suite of tests, simple click the Run button. If you want to run only one Test Fixture or even just a single test, you can double-click it in the tree.

The GUI App

There are situations, particularly when you want to have an automated build script run your tests, when the GUI app isn't appropriate. In these automated builds, you typically want to have the output posted to a website or another place where it can be publicly reviewed by the development team, management or the customer. The NUnit 2.0 console application takes the assembly as a command-line argument and produces XML output. You can then use XSLT or CSS to convert the XML into HTML or any other format. For more information about the console application, check out the NUnit documentation.

Doing Test-Driven Development

So now you know how to write unit tests, right? Unfortunately just like programming, knowing the syntax isn't enough. You need to have a toolbox of skills and techniques before you can build professional software systems. Here are a few techniques that you can use to get you started. Remember, however, that these tools are just a start. To really improve your unit testing skills, you must practice, practice, practice. If you are unfamiliar with TDD, what I'm about to say may sound a little strange to you. A lot of people have spent a lot of time telling us that we should carefully design our classes, code them up and then test them. What I'm going to suggest is a completely different approach. Instead of designing a module, then coding it and then testing it, you turn the process around and do the testing first. To put it another way, you don't write a single line of production code until you have a test that fails. The typical programming sequence is something like this:

  1. Write a test.
  2. Run the test. It fails to compile because the code you're trying to test doesn't even exist yet! (This is the same thing as failing.)
  3. Write a bare-bones stub to make the test compile.
  4. Run the test. It should fail. (If it doesn't, then the test wasn't very good.)
  5. Implement the code to make the test pass.
  6. Run the test. It should pass. (If it doesn't, back up one step and try again.)
  7. Start over with a new test!

While you are doing step #5, you create your code using a process called Coding by Intention. When you practice Coding by Intention you write your code top-down instead of bottom up. Instead of thinking, "I'm going to need this class with these methods," you just write the code that you want... before the class you need actually exists. If you try to compile your code, it fails because the compiler can't find the missing class. This is a good thing, because as I said above, failing to compile counts as a failing test. What you are doing here is expressing the intention of the code that you are writing. Not only does this help produce well-tested code, it also results in code that is easier to read, easier to debug and has a better design. In traditional software development, tests were thought to verify that an existing bit of code was written correctly. When you do TDD, however, your tests are used to define the behavior of a class before you write it. I won't suggest that this is easier than the old ways, but in my experience it is vastly better. If you have read about Extreme Programming, then this is primarily a review.

However, if this is new to you, here is a sample. Suppose the application that I'm writing has to allow the user to make a deposit in a bank account. Before creating a BankAccount class, I create a class in my testing library called BankAccountTests . The first thing I need my bank account class to do is be able to take a deposit and show the correct balance. So I write the following code:

namespace UnitTestingExamples.Tests
{
  using System;
  using NUnit.Framework;

  [TestFixture]
  public class BankAccountTests
  {
    [Test]
    public void TestDeposit()
    {
      BankAccount account = new BankAccount();
      account.Deposit( 125.0 );
      account.Deposit( 25.0 );
      Assertion.AssertEquals( 150.0, account.Balance );
    }
  }
}

Once this is written, I compile my code. It fails of course because the BankAccount class doesn't exist. This illustrates the primary principle of Test-Driven Development: don't write any code unless you have a test that fails. Remember, when your test code won't compile, that counts as a failing test. Now I create my BankAccount class and I write just enough code to make the tests compile:

namespace UnitTestingExamples.Library
{
  using System;

  public class BankAccount
  {
    public void Deposit( double amount )
    {
    }

    public double Balance
    {
      get { return 0.0; }
    }
  }
}

This time everything compiles just fine, so I go ahead and run the test. My test fails with the message "TestDeposit: expected: <150> but was <0>". So the next thing we do it write just enough code to make this test pass:

namespace UnitTestingExamples.Library
{
  using System;

  public class BankAccount
  {
    private double _balance = 0.0;

    public void Deposit( double amount )
    {
      _balance += amount;
    }

    public double Balance
    {
      get { return _balance; }
    }
  }
}

This time our tests pass.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.” - Rick Osborne