Can You Hear Me Now?

If I were just a bit of a cynic, I would say that open source is a nice spin on freeware. Freeware is free. Generally, freeware comes with source code, but calling free code open source seems to have a legitimizing quality about it. And, in fact, sometimes freeware carried a connotation that the code was hacky. I am not sure if it was Eric Raymond's great book The Cathedral and the Bazaar that elevated the status of freeware or great products such as NUnit, but whatever combination of events occurred, we programmers are benefiting from open source code.

In this article, I want to talk about a synergy that includes the excellent open source NUnit (www.nunit.org) testing tool, instrumenting your code for tracing, and TraceListeners. Collectively, these three aspects of .NET programming can make debugging and testing a ton of fun and help you come off as a real pro when you deliver bulletproof code.

Rather than assume you have had time to familiarize yourself with any of these things—TraceListeners, NUnit, and the notion of instrumenting your code—I will briefly introduce all three things and show you a nice way to tie them together. If you want more information about NUnit, for example, check earlier articles on www.codeguru.com (such as "Testing Visual Basic .NET with NUnit").

Instrumenting Your Code

Instrumenting is a term that is gaining in popularity. We programmers use the term to mean adding code that helps us diagnose and keep track of how our code is behaving. Years ago, we'd add some print statements and send some text to the console; this text might tell us that our code entered a specific method or dump some state information. With Visual Basic, we began using the Debug.Print method to send text to the Immediate window. In Visual Basic .NET, we can use Debug.WriteLine or Trace.WriteLine. Adding these statements to your code is part of what we mean by instrumenting your code.

To add Trace.WriteLine statements to your code, add an Imports statement for the System.Diagnostics namespace, and add some strategically placed Trace.WriteLine statements. (If you are not sure, a namespace is a thing that contains classes, structures, and enumerations.)

Suppose, for example, that we are writing an application that employs differential equations to calculate missile trajectories. Before we start blowing things up, we will clearly want to test our code. We will definitely want to watch the behaviors unfold as the code is running to prevent things such as firing before aiming. Simplifying here, we could write methods to aim and fire, adding Trace statement to each method.

Listing 1: Instrumenting for Tracing.

Option Explicit On
Imports System.Diagnostics

Public Class FiringSystem

  Public Sub Aim(ByVal Elevation As Double, _
    ByVal Direction As Double, ByVal Distance As Double)

    Const Message As String = _
      "Aiming with Elevation={0}, Direction={1}, Distance={2}"
    Trace.WriteLine(String.Format(Message, Elevation, Direction, _
                                  Distance))

    'Aim here
  End Sub

  Private Function WasAimed() As Boolean
    Throw New NotImplementedException()
  End Function

  Public Sub Fire()
    Debug.Assert(WasAimed())
    Trace.WriteLine("Fire in the hole")
    ' Blow up bad guys
  End Sub

End Class

In the example, our stubbed out FiringSystem class provides an Aim and Fire method. The Aim method will send a string containing the Elevation, Direction, and Distance to a TraceListener. (More on TraceListeners in a moment.) The Fire method asserts that we did in fact aim and emits a trace statement that indicates that Fire was called.

When we run our FiringSystem, the Trace.WriteLine statements tells us what is going on.

Implementing a TraceListener

A TraceListener is a class that inherits from the abstract class TraceListener. To listen for Trace statements, TraceListeners are added to the Trace.Listener collection. Listener is a shared collection on the Trace class. By default, a debug TraceListeners has already been added to the Listeners collection and output is written to the Output window in Visual Studio .NET. However, if we aren't running VS.NET, we won't see these messages. Suppose we want to trace after we deploy the software or in another environment? Then, we can implement our own TraceListener and have it send trace messages to a log file or database.

To implement your own TraceListener, create a new class that inherits from System.Diagnostics.TraceListener, implement a Write and WriteLine method, and add an instance of your class to the Trace.Listeners collection. Listing 2 demonstrates a simple TraceListener and Listing 3 demonstrates how to start listening.

Listing 2: Implementing a stripped down TraceListener.

Imports System.IO

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

Listing 3: Listening to our FiringSystem class.

Public Class FiringSystem

  Private Shared Listener As MyListener = New MyListener()

  Shared Sub New()
    If (Not Trace.Listeners.Contains(Listener)) Then
      Trace.Listeners.Add(Listener)
    End If
  End Sub
'... Elided for clarity

MyListener provides a stripped-down TraceListener. Our listener will hear Trace.Write and Trace.WriteLine statements that are called with string arguments and send these to the Console. To begin listening, we add a shared constructor to the FiringSystem class. The shared constructor is called before any other code is run; the shared constructor stuffs exactly one instance of MyListener into the Trace.Listeners collection.

For more on TraceListeners, read the December 2002 codeguru.com article "Implementing a Custom TraceListener."

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

To implement an NUnit test, you will need to download and install NUnit from www.nunit.org , create a class library, and add a TestFixture class and a couple of test methods. Listing 4 demonstrates.

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



Click here for a larger image.

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



Click here for a larger image.

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.

Summary

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 pkimmel@softconcepts.com.

# # #



Comments

  • One query

    Posted by gmeena1982 on 04/12/2007 01:43am

    Is there an option to write code like "ExpectedException(NotImplementedException.GetType())" instead of "ExpectedException(GetType(NotImplementedException)"? I wanted to get this clarified since I am getting the following error when i run my test cases using visual studio team system for developers: TestMethodName has invalid ExpectedException attribute. The type must inherit from Exception. Hoping to read your earliest response. Thanks.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

  • Managing your company's financials is the backbone of your business and is vital to the long-term health and viability of your company. To continue applying the necessary financial rigor to support rapid growth, the accounting department needs the right tools to most efficiently do their job. Read this white paper to understand the 10 essentials of a complete financial management system and how the right solution can help you keep up with the rapidly changing business world.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds