Developer

Reduce runtime errors in .NET through unit testing

Does testing code rank low on your favorite things to do list? If so, check out Tony Patton's demonstration of how simple it is to effectively unit test your code using NUnit, a unit-testing framework for all .NET languages.

Microsoft's submission of the C# language and the CLR (Common Language Runtime) as industry standards has led to many open source projects based on the .NET platform. One such notable project is NUnit, a unit-testing framework for all .NET languages. I'll demonstrate the simplicity of using NUnit to effectively unit test your code.

Unit testing

Even though you may love developing code, you may not find thoroughly testing the code very appealing. It ranks right behind documentation. However, it's much more cost-effective for a developer to catch and fix problems during code development than waiting for someone in QA to find the problem, which would require the developer to revisit the code. This is the idea behind unit testing.

Unit testing involves testing the public interfaces of your application classes. That is, you test the classes to verify they perform as expected. This encompasses verifying that results are returned as expected, as well as proper exception handling. NUnit is an excellent tool to aid with these tasks. NUnit is a flexible testing framework for the .NET platform. It provides an easy-to-use interface for running your test.

NUnit is freely available from the NUnit.org Web site for both the Windows and Mono environments. (I'm using a Windows XP machine for this article, but the examples have been tested with Mono on SUSE 9.2 Linux distribution.) Let's take a closer look at the NUnit framework in action.

Working with NUnit

The NUnit architecture is straightforward. You can be up and running with sample tests in minutes as opposed to hours. Once you install it on a system, the framework is located in the nunit.framework.dll file. This file should be made available to your project to develop NUnit-based code. A reference may be added via Visual Studio .NET, or you can place it in the bin directory of your application. Next, you must add a reference to the NUnit namespace to your code (or utilize the complete path to NUnit classes). The following C# snippet demonstrates this:

using NUnit.Framework;

Here's the VB.NET equivalent:

Imports NUnit.Framework

Now you can use the classes in the NUnit.Framework namespace. But let's examine the many NUnit attributes before diving into code.

NUnit attributes

The NUnit framework utilizes custom attributes to specify NUnit elements utilized in code. These attributes are placed before the appropriate code elements. Here is a list of these attributes:

  • ExpectedException includes an exception that the method can return.
  • Setup is an initialization section of code executed before the actual unit tests are run.
  • TearDown is a section of code run after all the tests have run.
  • Test is an individual unit test. This is placed before a method to indicate it is a test.
  • Ignore may be placed before a unit test to disable it. NUnit ignores any test with this attribute.
  • TestFixture is a collection of unit tests. This is placed before the class declaration to specify the class can contain tests.

NUnit executes Setup first, then individual tests (it executes in the order it's listed in code if it isn't specified otherwise like running manually), and the TearDown section runs last. NUnit doesn't execute any code marked Ignore. In addition, NUnit provides numerous assertions to be used for unit testing (you may also develop custom methods), which are available via these methods of the Assert class:

  • AreEqual asserts that two objects are equal. Two objects are considered equal if both are null or if they have the same value.
  • AreSame asserts that two objects refer to the same object.
  • IsFalse asserts that a condition is false.
  • IsNull asserts that an object is null.
  • IsNotNull asserts that an object is not null.
  • IsTrue asserts that a condition is true.

You use these assertions to unit test your code. The best way to demonstrate it is via a code sample. The following C# class will be tested using NUnit:

using System;
using NUnit.Framework;
namespace UnitTestingSampleClass {
public class ExampleClass {
public string ConvertToUpper(string val)     {
return val.ToUpper();
}
public string ConvertToLower(string val) {
return val.ToLower();
} } }

This is a very simple class that provides two methods. The first method converts a string to uppercase, and the second method does the reverse by converting to lowercase. You have your class, but you still need to develop a corresponding NUnit class to test it. The NUnit class follows:

using System;
using NUnit.Framework;
using UnitTestingSampleClass;
namespace UnitTestingSample {
[TestFixture]
public class TestClass {
private string test1 = null;
private string test2 = null;
ExampleClass testObj = null;
[SetUp]
public void init() {
testObj = new ExampleClass();
test1 = "techrepublic.com";
test2 = TECHREPUBLIC.COM";
}
[Test]
public void TestA() {
Assert.AreEqual(testObj.ConvertToLower(test2), test2.ToLower());
}
[Test]
public void TestB() {
Assert.AreEqual(testObj.ConvertToUpper(test1), test1.ToUpper());
}
[Test]
[ExpectedException(typeof(NullReferenceException))]
public void NullTest() {
Assert.AreEqual(testObj.ConvertToLower(null),"");
}
[Test]
[Ignore("Not Used")]
public void TestC() { }
[Test]
public void TestD() {
Assert.AreEqual(testObj.ConvertToLower(test1), test1.ToUpper());
}
[TearDown]
public void End() {
test1 = null;
test2 = null;
testObj = null;
} } }

This class contains five tests:

  • TestA: The ConvertToLower method is tested by passing in a string value and comparing to the result of the String class's ToLower method. True (test passed) is returned by Assert.AreEqual if they're equal; otherwise, false is returned, indicating the test failed.
  • TestB: The ConvertToUpper method is tested by passing in a string value and comparing to the result of the String class's ToUpper method. True (test passed) is returned by Assert.AreEqual if they are equal; otherwise, false is returned, indicating the test failed.
  • NullTest: Passing a null value to the ConvertToLower method is tested to test whether the appropriate Exception is thrown. The Assert.IsEqual test will pass (true) if both are equal or the specific exception is thrown (NullPointerException); otherwise, false is returned to fail the test.
  • TestC: This test is not coded, so it's tagged to be ignored.
  • TestD: This test fails since it compares a call to the ToLower method with a string converted to uppercase. This provides an example of what to expect with a failed test.

In addition, the init method runs before the tests (Setup) and the end (TearDown) method is called for cleanup once the tests have completed. NUnit includes a graphical interface for running the tests. You could test your sample with the following command line to call the interface and run the tests:

nunit-gui "c:\UnitTestingSample.exe" /run

The assembly was located in the root of the C drive on my system, so the path may be different on your system. You should specify the complete path to the assembly in the first parameter passed to nunit-gui. If you'd rather stick to the command line, you can use the command-line version with the following line:

nunit-console "c:\UnitTestingSample.exe"

This produced the following output on my system:

NUnit version 2.2.0
Copyright (C) 2002-2003 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov,
Charlie Poole..
Copyright (C) 2000-2003 Philip Craig.
All Rights Reserved.

OS Version: Microsoft Windows NT 5.1.2600.0    .NET Version: 1.1.4322.573

....N.F
Tests run: 4, Failures: 1, Not run: 1, Time: 0.1093876

Failures:
1) TestUnitTestingConsole.TestClass.TestD :
String lengths are both 11.
Strings differ at index 0.
expected:<"techrepublic.com">
but was:<"TECHREPUBLIC.COM">
—————-^

at TestUnitTestingConsole.TestClass.TestD() in c:\TestClass.cs:line 55

Tests not run:
1) TestUnitTestingConsole.TestClass.TestC : Not Used

Each approach provides numerous command-line options that you may view by using the /help command-line switch. The equivalent VB.NET code follows with the class to be tested listed first:

Public Class VBNetTestClass
Public Function ConvertToUpper(ByVal val As String) As String
Return val.ToUpper()
End Function
Public Function ConvertToLower(ByVal val As String) As String
Return val.ToLower()
End Function
End Class

This is the actual NUnit class for testing:

Imports NUnit.Framework
Imports UnitTestClassVBNet
<TestFixture()> Public Class Class1
Private test1 As String
Private test2 As String
Private testObj As VBNetTestClass
<SetUp()> Public Sub Init()
testObj = New VBNetTestClass
test1 = "techrepublic.com"
test2 = "TECHREPUBLIC.COM"
End Sub
<Test()> Public Sub TestA()
Assert.AreEqual(testObj.ConvertToLower(test2), test2.ToLower())
End Sub
<Test()> Public Sub TestB()
Assert.AreEqual(testObj.ConvertToUpper(test1), test1.ToUpper())
End Sub
<Test(), Ignore("sample ignore")> Public Sub TestC()
End Sub
<Test()> Public Sub TestD()
Assert.AreEqual(testObj.ConvertToLower(test1), test1.ToUpper())
End Sub
<Test(), ExpectedException(GetType(NullReferenceException))> Public Sub NullTest()
Assert.AreEqual(testObj.ConvertToLower(Nothing), "")
End Sub
<TearDown()> Public Sub CleanUp()
test1 = Nothing
test2 = Nothing
testObj = Nothing
End Sub
End Class

The sooner you catch runtime errors the better

Unit testing allows you to catch runtime errors much sooner than if you had to wait for a QA tester. In addition, it allows you to fix errors while the code is fresh in your memory. The process of creating tests before the actual code is gaining acceptance as well, but that's a topic for another time.

TechRepublic's free .NET newsletter, delivered each Wednesday, contains useful tips and coding examples on topics such as Web services, ASP.NET, ADO.NET, and Visual Studio .NET. Automatically sign up today!

About Tony Patton

Tony Patton has worn many hats over his 15+ years in the IT industry while witnessing many technologies come and go. He currently focuses on .NET and Web Development while trying to grasp the many facets of supporting such technologies in a productio...

Editor's Picks

Free Newsletters, In your Inbox