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
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
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
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
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.
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.
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.
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