Tracing Program Execution & NUnit

NUnit and .NET's TraceListeners help you eliminate bugs from code.


August 01, 2004
URL:http://www.drdobbs.com/windows/tracing-program-execution-nunit/184405769

August, 2004: Tracing Program Execution & NUnit

Instrumenting and listening to your code

Paul is chief architect for SoftConcepts and author of Advanced C# Programming (McGraw-Hill, 2002), among other books. He can be contacted at pkimmel softconcepts.com.


I like to watch The Discovery Channel television show American Chopper about the Teutul family (http://www.orangecountychoppers.com/) who build some amazing motorcycles. As well as being entertaining, the Teutuls build these very cool bikes in a short time from about 90 percent stock parts. According to the show, they turn around custom choppers in about a week. How do they do it and what can we learn from them?

The key to the success of Orange County Choppers is that they do use stock parts and most of the pieces and some of the labor are supplied by specialists. For instance, Paul Teutul, Jr. may start with a stock frame, or request a custom frame with a some pretty basic specifications. On the "Fire Bike" episode, for example, they added a custom carburetor that looked like a fire hydrant. The key elements here are that a carburetor is a well-defined pattern and the engineers that built it are experts at building carburetors. Collectively, the Teutuls use stock parts and improvise selectively for the extra cool factor.

In our business, stock tools are existing components and frameworks. The more we use stock components, frameworks, and rely on experts, the less time we have to expend on meeting deadlines. In addition, this surplus of energy and time can be exploited to add the fit-and-finish that exceeds expectations and excites users. To this end, in this article I examine the open-source NUnit tool and the .NET Framework's TraceListeners that give you a means of easily and professionally eliminating bugs from code.

Instrument Code As You Write It

To instrument code means to add diagnostics code that lets you monitor and diagnose the code as it runs. One form of instrumenting code is to add Trace statements that tell you what the executing code is really doing. Granted, tracing has been around a while, but only recently has it been included in the broader concept referred to as "instrumenting" code.

Adding Trace statements as you progress is a lot easier and more practical than adding them after the solution code has been written. Instrumenting as you go is better because you know more about the assumptions you are making when you are implementing the solution.

The basic idea is simple: When you write a method or property, add a Trace statement that indicates where the instruction pointer is at and what's going on. This is easy to do. If you are programming in C#, add a using statement that refers to the System.Diagnostics namespace and call the static method Trace .WriteLine statement, passing a string containing some useful text.

The Trace class maintains a static collection of TraceListeners and one Trace statement multicasts to every listener in the collection. This implies—and is the case, in fact—that you can create a custom TraceListener. A default TraceListener sends information to the Output window in VS.NET, which is always in the listener's collection, but you need to be running VS.NET to see these messages.

In addition to supporting custom TraceListeners, the .NET Framework facilitates postdeployment turning tracing on/off. This means, you can instrument your code with Trace statements, leave them in when you deploy your code, and turn them back on in the field—modifying the application or machine's external XML config file.

Unit Test Frequently

The next thing you need is a process for testing chunks of code. Testing should occur early and often, and it is more practical and prudent to test in iterations, especially since NUnit makes it so easy. Returning to the custom chopper analogy, I guarantee you that the company supplying motors to the Teutuls turns them over and run them a bit before they ever get on a bike. This is because the motor is an independent, testable entity, a component if you will.

NUnit (http://www.nunit.org/) is built with .NET. NUnit 2.1 is a unit-testing framework for .NET languages. Although originally modeled after JUnit, NUnit is written in C# and takes advantage of numerous .NET language features. To test code, all you need do is download and install NUnit. Then create a class library and add some attributes to classes containing test code. .NET custom attributes defined by the nunit.framework namespace are used to tag classes as test fixtures and methods for initialization, deinitialization, and as tests.

For example, Listing One is the the canonical HelloWorld.exe application, and Listing Two is a class library that implements tests. In Listing One, a sample class keeps down the noise level. Greetings shows you what you need to see, the Trace.WriteLine statement. (I use Trace statements in methods with this low level of complexity if the method is important to the solution domain.)

In Listing Two, I add a reference to the library containing the Greetings class and a using statement introducing its encompassing namespace. Next, I add a reference to the nunit.framework.dll assembly and its namespace. After that, you just need a class tagged with the TestFixtureAttribute—dropping the attribute suffix by convention—and the TestAttribute on public methods that return void and take no arguments.

If you load the test library in NUnit, it takes care of the rest. A green for "pass" and red for "fail" (see Figure 1) removes all ambiguity from the testing process.

NUnit was written by .NET Framework experts. If you look at the NUnit source, you see that they knew how to dynamically create AppDomains and load assemblies into these domains. Why is a dynamic AppDomain important? What the dynamic AppDomain lets NUnit do is to leave NUnit open, while permitting you to compile, test, modify, recompile, and retest code without ever shutting down. You can do this because NUnit shadow copies your assemblies, loads them into a dynamic domain, and uses a file watcher to see if you change them. If you do change your assemblies, then NUnit dumps the dynamic AppDomain, recopies the files, creates a new AppDomain, and is ready to go again.

The collective result is that NUnit facilitates testing while reducing the amount of scaffolding you have to write to run the tests and eliminates the time between testing, modifying, and retesting code. You focus on the code to solve the problem and tests, not the testing utility itself.

Listening to Your Code

Once you have instrumented your code with Trace statements and written NUnit tests, wouldn't it be nice to be able to see the output from those Trace statements?

Remember that the Trace class writes to every listener in the Trace.Listeners collection. All you need to do is implement a custom TraceListener and NUnit tells you if a test passed or failed, and shows you what's going on behind the scenes. Listing Three shows how to implement a sufficient TraceListener for NUnit. (The new code is shown in bold font.) Inside the file containing the TestFixture, I added a custom TraceListener. The custom listener overrides the Write and WriteLine methods and sends the message to the Console. NUnit redirects standard output (the Console) to NUnit's Standard Out tab (Figure 2). To finish up, you stuff the listener in the Trace.Listeners collection. Now that you have NUnit listening for Trace messages, you can run the tests and all of your Trace statements are written to the Standard Out tab. When the tests are all green, things are going okay.

Conclusion

If you instrument your code with Trace statements, define a custom listener, and use NUnit tests, you have some powerful but easy-to-use code working on your behalf. Making this a regular part of your software development process goes a long way in speeding up a better end result.

DDJ



Listing One

using System;
using System.Diagnostics;
namespace TestMe
{
  public class Greetings
  {
    public static string GetText()
    {
      Trace.WriteLine("Greetings.GetText called");
      return "Hello, World";
    }
  }
}
Back to article


Listing Two
using NUnit.Framework;
using TestMe;

namespace Test
{
  [TestFixture()]
  public class MyTests
  {
     [SetUp()]
     public void Init()
     {
       // pre-test preparation here
     }
    [TearDown()]
    public void Deinit()
    {
      // post-test clean up
    }
    [Test()]
    public void GreetingsTest()
    {
      Assertion.AssertEquals("Invalid text returned", "Hello, World", 
      Greetings.GetText());
    }
  }
}
Back to article


Listing Three
using System;
using System.Diagnostics;
using NUnit.Framework;
using TestMe;
namespace Test
{
  public class Listener : TraceListener
  {
    public override void Write(string message)
    {
      Console.Write(message);
    }
    public override void WriteLine(string message)
    {
      Console.WriteLine(message);
    }
  }
  [TestFixture()]
  public class MyTests
  {
    private static Listener listener = new Listener();
    [SetUp()]
    public void Init()
    {
      if( !Trace.Listeners.Contains(listener))
        Trace.Listeners.Add(listener);
    }
    [TearDown()]
    public void Deinit()
    {
      Trace.Listeners.Remove(listener);
    }
    [Test()]
    public void GreetingsTest()
    {
      Assertion.AssertEquals("Invalid text returned", "Hello, World", 
      Greetings.GetText());
    }
  }
}
Back to article

August, 2004: Tracing Program Execution & NUnit

Figure 1: The test passed.

August, 2004: Tracing Program Execution & NUnit

Figure 2: Listening for Trace messages in NUnit.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.