Can You Hear Me Now?
Testing with NUnit
Assume that our code now is sufficiently implemented and we want to begin testing. NUnit is built with .NET and employs the great .NET framework to make testing easy. (Refer to "Testing Visual Basic .NET with NUnit" on codeguru.com, February, 2003.)
At this point, we can write a couple of tests: We can test that Aim works and we can test to ensure that Fire throws the NotImplementedException. (We want to delay actually firing until we have really bullet-proofed this code. So, in the example, we are testing that we won't accidentally fire yet.)
Listing 4: Implementing a NUnit test for the FiringSystem class.
Imports NUnit.Framework <TestFixture()> _ Public Class Test <SetUp()> Public Sub Init() End Sub <TearDown()> Public Sub Deinit() End Sub <Test()> Sub AimTest() Dim FS As CanYouHearme.FiringSystem = _ New CanYouHearme.FiringSystem() FS.Aim(1000, 90, 10000) End Sub <Test(), ExpectedException(GetType(NotImplementedException))> _ Sub FireTest() Dim FS As CanYouHearme.FiringSystem = _ New CanYouHearme.FiringSystem() FS.Fire() End Sub End Class
In the example, we stubbed out some setup and teardown code and two tests. The first test—AimTest—creates an instance of the FiringSystem class and calls Aim. The second test is tagged with the TestAttribute and ExpectedExceptionAttribute. The first attribute indicates that FireTest is an NUnit test, and the second attribute indicates that we should get a NotImplementedException from the FiringSystem.
After we compile the test library, we can load it in NUnit and run the tests. All greens in the GUI version of NUnit means that all of our tests passed (see Figure 1).
Figure 1: Green means a test passed; all green is all good.
Eavesdropping in NUnit with a TraceListener
We have one last thing to do. Remember all of that great Trace code we added to the FiringSystem? Why should we let it go to waste? We can add a TraceListener to our NUnit test library and watch what is going on in the background as our code is running in NUnit.
To add a TraceListener, we can copy and paste the MyTraceListener class into the module containing our NUnit tests and wire the TraceListener into NUnit. Listing 5 demonstrates the complete addition.
Listing 5: Adding a custom TraceListener to NUnit.
Imports System.Diagnostics Imports NUnit.Framework <TestFixture()> _ Public Class Test Public Class MyListener Inherits System.Diagnostics.TraceListener Public Overloads Overrides Sub Write(ByVal Message As String) Console.Write(Message) End Sub Public Overloads Overrides Sub WriteLine(ByVal Message _ As String) Console.WriteLine(Message) End Sub End Class Private Shared Listener As MyListener = New MyListener() <SetUp()> Public Sub Init() If (Not Trace.Listeners.Contains(Listener)) Then Trace.Listeners.Add(Listener) End If End Sub <TearDown()> Public Sub Deinit() Trace.Listeners.Remove(Listener) End Sub <Test()> Sub AimTest() Dim FS As CanYouHearme.FiringSystem = _ New CanYouHearme.FiringSystem() FS.Aim(1000, 90, 10000) End Sub <Test(), ExpectedException(GetType(NotImplementedException))> _ Sub FireTest() Dim FS As CanYouHearme.FiringSystem = _ New CanYouHearme.FiringSystem() FS.Fire() End Sub End Class
In the revision, we added an internal, nested class to our NUnit TestFixture. This nested class is a custom TraceListener. When a test is started, the subroutine tagged with the SetUpAttribute is run first; in our example, this puts the shared instance of MyListener into the Trace.Listeners collection. Now, when our tests are run, NUnit can eavesdrop, receiving Trace statements from our code and displaying them in the Standard Out tab of the NUnit GUI (see Figure 2).
Figure 2: The Trace statements are now sent to the Standard Out tab of the NUnit GUI.
Why did we get two Aim trace statements? The answer is that our original listener was implemented to write to the standard output device, the console. NUnit redirects the standard output device to the Standard Out tab of NUnit. Hence, if we implement a custom TraceListener that writes to the Console, we don't need to put one in the test library itself. However, if you send Trace statements to the EventLog as an alternative, you can implement a custom listener, as shown in Listing 5, that writes to the Console object.
This article exemplifies what a framework can really do for us. The .NET framework permits us to tie everything together to create a synergistic and unified whole. The framework provides a lot of good classes, such as attributes, Console, Trace, and TraceListener, allowing the NUnit developers to extend the framework to build a great product, and we can tap into the framework from NUnit, yielding a cohesive suite of tests that uses existing instrumentation.
Writing unit tests with NUnit and instrumenting your code can yield excellent results, yet all of this code was relatively easy to write because it is supported by a coherent and cohesive framework. Achieving this same effect in VB6, for example, would be very difficult and time consuming, really placing VB6 developers at distinct a disadvantage to VB.NET developers.
About the Author
Paul Kimmel has written several books on Visual Basic, including the recently released Visual Basic .NET Power Coding from Addison Wesley. Pick up your copy at www.amazon.com. Paul is also available for short- and long-term consulting, public speaking, and .NET training. You may contact him at firstname.lastname@example.org.
# # #
Page 2 of 2