http://www.developer.com/

Back to article

Frictionless Testing with TestDriven.NET


December 30, 2004

Much has been written in the last several years about the benefits of unit testing and test-driven development. The basic idea is that you should write a test for every bit of functionality in your application, and exercise these tests as you go along to make sure that you didn't break anything. Proper use of these techniques can lead to increased confidence and fewer errors in your code. A number of tools, such as NUnit, MbUnit, and even (for those of you brave enough to run the Visual Studio 2005 beta) Microsoft Team System now support unit testing for .NET development. But a new entrant to the .NET ecosphere, TestDriven.NET, makes the process easier than ever. In this article, I'll show you how.

Writing and Running Your First Test

Other tools, such as NUnit, run as external applications. They test your code outside of the IDE. TestDriven.NET, in contrast, hooks unit testing directly up to Visual Studio .NET. It can also make use of any of the testing frameworks I've already mentioned; it comes with a bundled copy of MbUnit, so that's what I'll use here. To get started, download a copy of TestDriven.NET and install it. The download is free, although you do need to register with the site to get it. Then create a new Visual Basic .NET class library project using Visual Studio .NET 2003.

The simple project I'll develop will let callers determine some properties of numbers. I've added two classes to the project: Number.vb, which will contain the actual code for the class, and NumberTests.vb, which will contain test code. To see the mechanics of TestDriven.NET, I'll start with a simple implementation of Number. The first thing this class needs to do is to be able to set the value of the number and retrieve it. Here's a skeleton that defines the proper interface:


Public Class Number

    Public Sub New()

    End Sub

    Public Property Value() As Integer
        Get

        End Get
        Set(ByVal Value As Integer)

        End Set
    End Property

End Class

You're probably looking at that code and thinking that there's no way that it could possibly work - and you're right. But one of the tenets of test-driven development is to write tests before you write code. That way you can be sure you know why you're writing each bit of code. So, let's move on to write a test.

Because I'm going to use MbUnit for the actual testing, I need to add references to MbUnit's two libraries: MbUnit.Core.dll and MbUnit.Framework.dll. These libraries get installed to the MbUnit folder beneath the TestDriven.NET install location. With the MbUnit objects available, it's time to write the start of the test class:


Imports System
Imports MbUnit.Core.Framework
Imports MbUnit.Framework

 _
Public Class NumberTests

     _
    Public Sub TestValue()
        ' Make sure we can store and retrieve a value
        Dim num As New Number
        num.Value = 5
        Assert.AreEqual(num.Value, 5)
    End Sub

End Class

MbUnit, like the other .NET unit-testing frameworks, uses attributes to carry metadata. The TestFixtureAttribute attribute indicates that this class contains tests. The Test attribute on the TestValue method indicates that this method is a unit test. This particular test instantiates a Number object, sets its value, and then tests the value using the MbUnit Assert object. If the two arguments to the AreEqual method are the same, the test is a success; otherwise, it's a failure.

Now it's time to see how easy it is to run a test using TestDriven.NET. Right-click in the test class and select Run Test(s). TestDriven.NET will compile the project, find the appropriate tests, run them, and display the results in the Visual Studio .NET output pane. In this case, the output ends with the ominous warning:


0 succeeded, 1 failed, 0 skipped, took 0.00 seconds

Yes, the test failed (which is hardly surprising, because the Value property is only partially implemented). Even better, MbUnit saves its output as an HTML file, and TestDriven.NET obligingly provides a link to this file in the output pane. Control-click the link to open the output report right in the IDE, as shown in Figure 1. As you can no doubt guess, the unhappy red colors indicate that not all is well here.



Click here for a larger image.

Making Things Work

Now that you know for sure that the code in Number.vb isn't working, it's time to fix it. Here's a revised version of that class:


Public Class Number

    Public Sub New()

    End Sub

    Private _value As Integer

    Public Property Value() As Integer
        Get
            Return _value
        End Get
        Set(ByVal Value As Integer)
            _value = Value
        End Set
    End Property

End Class

Now if you run the unit test, you'll find that the output window reads:


1 succeeded, 0 failed, 0 skipped, took 0.00 seconds.

The MbUnit report turns a soothing, happy green with passed tests, too. With this little bit of functionality working, you can go on to write more code. But remember: write tests first, then write code. After a few iterations of this procedure, you might end up with a Number class that looks like this:


Public Class Number

    Public Sub New()

    End Sub

    Private _value As Integer

    Public Property Value() As Integer
        Get
            Return _value
        End Get
        Set(ByVal Value As Integer)
            _value = Value
        End Set
    End Property

    Public ReadOnly Property IsEven() As Boolean
        ' Returns true if the number is even
        Get
            Return _value Mod 2 = 0
        End Get
    End Property

    Public ReadOnly Property IsOdd() As Boolean
        ' Returns true if the number is odd
        Get
            Return _value Mod 2 = 1
        End Get 
    End Property

    Public ReadOnly Property IsPositive() As Boolean
        ' Returns true if the number is positive
        Get
            Return _value > 0
        End Get
    End Property
End Class

And here's the corresponding test class:


Imports System
Imports MbUnit.Core.Framework
Imports MbUnit.Framework

 _
Public Class NumberTests

     _
    Public Sub TestValue()
        ' Make sure we can store and retrieve a value
        Dim num As New Number
        num.Value = 5
        Assert.AreEqual(num.Value, 5)
    End Sub

     _
    Public Sub TestIsEven()
        ' Make sure even numbers are property identified
        Dim num As New Number
        num.Value = 5
        Assert.AreEqual(num.IsEven, False)
        num.Value = 8
        Assert.AreEqual(num.IsEven, True)
    End Sub

     _
    Public Sub TestIsOdd()
        ' Make sure odd numbers are property identified
        Dim num As New Number
        num.Value = 5
        Assert.AreEqual(num.IsOdd, True)
        num.Value = 8
        Assert.AreEqual(num.IsOdd, False)
    End Sub

     _
    Public Sub TestIsPositive()
        ' Make sure positive numbers are property identified
        Dim num As New Number
        num.Value = -1
        Assert.AreEqual(num.IsPositive, False)
        num.Value = 0
        Assert.AreEqual(num.IsPositive, False)
        num.Value = 1
        Assert.AreEqual(num.IsPositive, True)
    End Sub

End Class

You'll note that the test class is actually longer than the code that it's testing. This isn't unusual if you're doing a thorough job of test-driven development, because you'll usually want to test a variety of inputs for each part of your code. Figure 2 shows the MbUnit output for the classes in this state, with every test passing.

Successful test report from MbUnit

But Wait, There's More!

So far, everything I've shown you could be done with any of the major unit testing frameworks, though TestDriven.NET makes it easier by keeping it all under one roof. But TestDriven.NET goes further to provide you with some advanced capabilities as well. For example, if a test fails, it adds an entry to the Visual Studio .NET task list. Double-clicking this entry takes you straight to the failing test. The "Run Test(s)" menu items is also integrated with Solution Explorer, so you can choose to run all the tests in a particular project or class easily.

TestDriven.NET also allows you to choose between in-proc and debugger testing with a simple menu selection. Normally, when you run your tests they just run, and you look to your actual code for problems. But what do you do when you suspect that the test itself might be in error? With TestDriven.NET, you set a breakpoint in the test and choose to run the tests in the debugger, as shown in Figure 3. When the test assembly hits the breakpoint, you can use all the regular debugging tools to figure out what might be wrong. This is possible in other unit testing tools (by attaching the debugger to the external process) but TestDriven.NET makes it a whole lot easier.

Choices for running tests

Two other features deserve mention as well. First, TestDriven.NET runs in its own process, which means that it's very unlikely to cause side effects with the code that it's testing. This also means that it can be stopped easily - in fact, when you're running tests, there's a Visual Studio .NET menu item to abort the rest of the test run. That's very useful when some test is taking much longer than expected. Second, you can run any method as an "ad-hoc test". Put the cursor inside it and choose to run tests, and you can see the output to the output pane. There are some limits to this facility (notably, there's no way to set input parameters), but it's a great way to experiment quickly with code.


About the Author

Mike Gunderloy is the author of over 20 books and numerous articles on development topics, and the lead developer for Larkware. Check out his latest book, Coder to Developer from Sybex. When he's not writing code, Mike putters in the garden on his farm in eastern Washington state.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date