Chapter 10. Unit Testing with Nunit

 

Testing by itself does not improve software quality. Test results are an indicator of quality, but in and of themselves, they don’t improve it. Trying to improve software quality by increasing the amount of testing is like trying to lose weight by weighing yourself more often. What you eat before you step onto the scale determines how much you will weigh, and the software development techniques you use determine how many errors testing will find. If you want to lose weight, don’t buy a new scale; change your diet. If you want to improve your software, don’t test more; develop better.

 
 --Steve C. McConnell, “Code Complete”

Testing is an important aspect of any software project, and there are many different kinds of tests that can be performed. An important, yet often misunderstood or ignored method of testing is the unit test. Unit testing is an inexpensive way that developers can write better code—faster. Large companies tend to spend a lot of time and resources on testing, yet usually do so near the end of a project, often meaning the testing is minimized or reduced because of budget and schedule constraints. In actuality, testing should be done extremely early in development, as well as continuously thereafter.

Programmers generally think of testing as a nuisance, because they would rather be writing code. Unit testing is not a grand quality initiative for large companies; unit testing is done by programmers for programmers. Many developers write throw-away code to test functionality, but doing so can introduce some problems and decrease the credibility of the test cases.

It is important to note that this chapter does not attempt to sell you on the idea of testing, as it is assumed that you have adopted this excellent practice already, since you are reading the chapter. Additionally, this chapter will only briefly cover the basics behind unit testing; it will in no way attempt to cover all the fundamentals of unit testing. The main focus of this chapter is on performing unit tests with the NUnit framework and application.

Overview of Unit Testing

Basically, unit testing focuses on a single unit—the class. Each class is tested alone in an attempt to discover errors in its code. The idea is to test anything in a class that could conceivably fail. If something in the class is changed, all tests, not just your own, are run again. If any fail, the programmer immediately goes back, fixes the problem, and runs the tests again. This process is performed in an iterative manner until all tests are successful.

After unit testing is complete on a group of modules, they are combined into progressively more complex groupings, which are also tested. This integration processes will continue until the entire application has been assembled and tested.

There are two main approaches when performing unit testing, as discussed in Table 10.1.

Table 10.1. Unit Testing Approaches

Approach

Description

Black-Box Approach

The black-box approach is the most commonly used method, in which each class represents an encapsulated object. The black-box approach is driven by all the preconstruction specifications for each class. Each item in the specification becomes a test, and several test cases are developed for it. The tests are focused on whether or not the class meets the requirements in the specification, rather than the programmer’s interpretation.

White-Box Approach

The white-box approach is based on the method specifications associated with each class. The white-box approach is generally used instead of the black-box approach when the complexity of the class is high. The tester may discover errors or assumptions by looking through the code that are not generally obvious to a tester using the black-box approach.

There are quite a few benefits to unit testing, but some of the most notable ones are discussed in Table 10.2.

Table 10.2. Unit Testing Benefits

Benefit

Description

Requires the programmer to slow down and think

When refactoring or adding a new feature, testing forces you to think about what the code is supposed to do. You end up thinking about how the public API is accessed and what the outcome should be, ending up with a clean design that does exactly what you expect it to do.

Protects you against other programmers

Sometimes bugs only manifest themselves in rare situations. If another programmer changes a class but does not run the new code with all the problematic situations, bugs may slip through. If a unit test exists to test that particular situation, then the bug will be found when the unit tests are run again after changing the code.

Forces you to design better code

Testing forces you to make your code easy to test, relying less on the usage of singletons and global variables. Tightly coupled design is often difficult to test and generally requires complex initialization. Testing generally enforces loosely coupled design to make testing easier.

Promotes refactoring without breaking code

Testing allows you to refactor at any time without the fear of breaking your code, so that the design of your program can improve over numerous iterations. Each time the code is changed, the tests are run again to ensure that all the existing modules remain stable.

Introducing NUnit

In order to properly perform unit testing, a framework must be employed to facilitate the testing. This is where NUnit comes into play. NUnit is a unit testing tool for the Microsoft .NET Framework. It targets testdriven development with all .NET languages, including C#, Visual Basic .NET, J#, and C++/CLI.

NUnit was developed by Jim Newkirk, Alexei Vorontsov, Michael Two, and Charlie Pool, based on the original NUnit version by Philip Craig. NUnit is very similar to the eXtreme Programming test frameworks (xUnits) with a couple of significant differences.

Just like .NET, the NUnit framework is language-independent, in the sense that any CLR-compliant language may be used to write tests and NUnit will execute them just fine.

Attributes are a wonderful feature of .NET, and are used by NUnit to identity tests and test fixtures, without requiring that tests inherit from classes within a testing framework. Using attributes to define tests allows code to remain clean and fairly independent of any test support files.

With the creation of tests, you perform the testing by launching either the GUI or console version of the NUnit application, and target the assemblies that you wish to test. NUnit uses reflection to interrogate the assemblies for tests and then executes them one at a time. All tests have the ability to execute setup and teardown methods, allowing for each test to be independent of the others.

Creating an NUnit Project

There are a few ways you can develop your unit tests. Some developers prefer to place test functions directly inside the source code of the project that is being tested. If this is something you wish to do, be sure to use the #if and #endif preprocessor tags to strip unit tests from release mode.

Other developers like to place tests inside separate files within the project being tested. Again, don’t forget to strip these tests out in release mode.

The most common approach, unless you’re testing internal objects, is to build your tests in external assemblies. The benefit to this approach is that all test code is decoupled from the project itself.

Use whichever method you are comfortable with. The example for this chapter has the test code in a separate assembly. Start by creating a new class library project for your unit test assembly.

The next thing to do is reference nunit.framework.dll in your unit test assembly. If you installed NUnit using the typical approach, you should have all the NUnit assemblies installed into the Global Assembly Cache (GAC). If not, you can press the Browse button and manually navigate to the assembly in the installation folder. The default installation path for the NUnit framework is C:Program FilesNUnit 2.2in.

Figure 10.1 shows the Add Reference dialog with the nunit.framework assembly showing up in the GAC.

Add Reference dialog for the NUnit framework.

Figure 10.1. Add Reference dialog for the NUnit framework.

After the NUnit framework reference has been added to your unit test project, you should have something similar to Figure 10.2. Also be aware that Visual Studio automatically adds System.Data and System.Xml, and they have been removed from the references list because they are not needed for this example.

Overview of the example project structure.

Figure 10.2. Overview of the example project structure.

The SimpleLibrary project contains the object we want to test, and the SimpleLibrary.UnitTesting contains the unit tests that NUnit will execute against SimpleLibrary.

Attribute Overview

Traditionally, NUnit provided test declaration using inheritance, but this design posed some problems with languages like C#, where multiple inheritance is not supported, and the only way to use the test framework is with complex inheritance hierarchies. The latest version of NUnit now offers a method of declaring tests with attributes, which is basically a .NET feature that can inject meta-data into an object.

Attributes do not reflect the code being run, but attributes do provide extra information about a particular object. The NUnit runner scans all the targeted assemblies for classes and methods that contain certain attributes and acts on them accordingly.

[TestFixture]

A class containing the methods that make up the testing performed on a class is marked with the [TestFixture] attribute. A common naming convention used is to take the name of the class you want to test, and append Tests on to the end. For example, if we are testing SimpleClass, it is common to name our test fixture SimpleClassTests. These will be the names used in the provided examples.

The following code snippet shows how to use this attribute:

[TestFixture]
public class SimpleClassTests
{
}

Note

Classes marked with the [TestFixture] attribute must have a public default constructor or no constructors at all. Without any constructors, a public default constructor will be created implicitly.

[Test]

A method in a test fixture marked with the [Test] attribute will be executed when the test fixture is tested with NUnit.

The following code snippet shows how to use this attribute:

[TestFixture]
public class SimpleClassTests
{
    [Test]
    public void TestSomething()
    {
    }
}

Note

It is important that a [Test] method be marked public, return void, and not take in any parameters.

[SetUp]

A method in a [TestFixture] marked with the [SetUp] attribute will be executed immediately before each test is run.

The following code snippet shows how to use this attribute:

[TestFixture]
public class SimpleClassTests
{
    [SetUp]
    public void SetUp()
    {
        // Do some initialization for the tests
    }
}

Note

It is important that a [SetUp] method be marked public, return void, and not take in any parameters.

[TearDown]

A method in a [TestFixture] marked with the [TearDown] attribute will be executed immediately after each test is run.

The following code snippet shows how to use this attribute:

[TestFixture]
public class SimpleClassTests
{
    [TearDown]
    public void TearDown()
    {
        // Do some cleanup for the tests
    }
}

Note

It is important that a [TearDown] method be marked public, return void, and not take in any parameters.

[Ignore]

There may be times when you want to temporarily disable a test or test fixture from being run, and without commenting out the code for it so that you are still reminded of the exemption within NUnit. Any method or class marked with either the [Test] or [TestFixture] attribute can be marked with the [Ignore] attribute.

This attribute causes the test or test fixture to be exempt from testing. This attribute must accept a string parameter describing why the test or test fixture is ignored.

The following code snippet shows how to use this attribute:

[TestFixture]
public class SimpleClassTests
{
    [Test]
    [Ignore("Broken functionality at the moment, so testing is pointless")]
    public void TestSomething()
    {
    }
}

[ExpectedException]

There may be situations where it is expected that an exception should be thrown, and this attribute exists to avoid the need for an ugly try-catch block. When a test is marked with the [ExpectedException] attribute and the expected exception that is specified in this attribute is thrown, the test is still successful. The only way a thrown exception will cause this test to fail is if the exception is not the same type specified using this attribute. Also keep in mind that multiple [ExpectedException] attributes can be specified if more than one expected exception should be ignored.

The following code snippet shows how to use this attribute:

[TestFixture]
public class SimpleClassTests
{
    [Test]
    [ExpectedException(typeof(InterfaceDesignerException))]
    public void TestSomething()
    {
    }
}

Caution

You must be very specific when stating the expected exception since NUnit is not aware of exception inheritance. If the exception thrown is derived from InterfaceDesignerException, the test would fail. The expected exception stated must be identical to the exception that will be thrown.

[Explicit]

If a test or a test fixture is marked with the [Explicit] attribute, the only way it will run is when it has been explicitly selected in the GUI to run, or passed to the command line version.

The following code snippet shows how to use this attribute:

[TestFixture]
public class SimpleClassTests
{
    [Test, Explicit]
    public void ExplicitTest()
    {
    }
}

[TestFixture, Explicit]
public class ExplicitTests
{
}

Note

If NUnit encounters an explicit test, it will treat the test as if it were marked with the [Ignore] attribute.

[Category]

There may be times when you want to categorize or group related tests, especially when working with a project of reasonable size. The [Category] attribute can be used to specify a category name for a test or test fixture to group it with other tests or test fixtures sporting the same category name.

When a specific category is selected to run, only tests or test fixtures belonging to the selected category are run.

The following code snippet shows how to use this attribute:

[TestFixture]
public class SimpleClassTests
{
    [Test, Category("ProcessorIntensive")]
    public void TestLongRunningProcess()
    {
    }
}

Note

When a specific category is selected to run, only tests or test fixtures belonging to the selected category are run.

Expected Outcome Assertion

The Assert class is used within test methods to verify known values and conditions. For example, Assert.AreEqual() can be used after running a particular test to confirm that a property has a specific value.

Following are the static member methods for the Assert class and an overview of what each one does:

Assert.AreEqual()

This comparison method tests for equality, and is perhaps the best assertion to use because both the expected and actual values are reported. Also, the overloaded signatures allow for equality comparison between equal values of varying numeric types. This allows for assertions like the following to succeed:

Assert.AreEqual(7, 7.0);

The following are all the method signatures available for this method:

Assert.AreEqual(int expected, int actual);
Assert.AreEqual(int expected, int actual, string message);
Assert.AreEqual(int expected, int actual, string message, object[] parameters);
Assert.AreEqual(decimal expected, decimal actual);
Assert.AreEqual(decimal expected, decimal actual, string message);
Assert.AreEqual(decimal expected, decimal actual, string message, object[] parameters);
Assert.AreEqual(float expected, float actual, float tolerance);
Assert.AreEqual(float expected, float actual, float tolerance, string message);
Assert.AreEqual(float expected, float actual, float tolerance, string message,
          object[] parameters);
Assert.AreEqual(double expected, double actual, double tolerance);
Assert.AreEqual(double expected, double actual, double tolerance, string message);
Assert.AreEqual(double expected, double actual, double tolerance, string message,
          object[] parameters);
Assert.AreEqual(object expected, object actual);
Assert.AreEqual(object expected, object actual, string message);
Assert.AreEqual(object expected, object actual, string message, object[] parameters);
Assert.AreSame(object expected, object actual);
Assert.AreSame(object expected, object actual, string message);
Assert.AreSame(object expected, object actual, string message, object[] parameters);

The following code snippet shows how to use this comparison method:

[TestFixture]
public class SimpleClassTests
{
    [Test]
    public void TestSomething()
    {
        string testData = "Hello World";
        Assert.AreEqual("Hello World", testData", "The strings do not match!");
    }
}

Assert.AreSame()

This comparison method tests that same objects are referenced by both arguments.

The following are all the method signatures available for this method:

Assert.AreSame(object expected, object actual);
Assert.AreSame(object expected, object actual, string message);
Assert.AreSame(object expected, object actual, string message, object[] parameters);

The following code snippet shows how to use this comparison method:

[TestFixture]
public class SimpleClassTests
{
    [Test]
    public void TestSomething()
    {
        object object1 = this;
        object object2 = this;
        Assert.AreSame(object1, object2, "The objects do not match!");
    }
}

Assert.IsTrue()

This condition method tests that the condition parameter evaluates to true.

The following are all the method signatures available for this method:

Assert.IsTrue(bool condition);
Assert.IsTrue(bool condition, string message);
Assert.IsTrue(bool condition, string message, object[] parameters);

The following code snippet shows how to use this comparison method:

[TestFixture]
public class SimpleClassTests
{
    [Test]
    public void TestSomething()
    {
        bool testData = true;
        Assert.IsTrue(testData, "testData is false!");
    }
}

Assert.IsFalse()

This condition method tests that the condition parameter evaluates to false.

The following are all the method signatures available for this method:

Assert.IsFalse(bool condition);
Assert.IsFalse(bool condition, string message);
Assert.IsFalse(bool condition, string message, object[] parameters);

The following code snippet shows how to use this comparison method:

[TestFixture]
public class SimpleClassTests
{
    [Test]
    public void TestSomething()
    {
        bool testData = false;
        Assert.IsFalse(testData, "testData is true!");
    }
}

Assert.IsNull()

This condition method tests that the condition parameter evaluates to null.

The following are all the method signatures available for this method:

Assert.IsNull(object anObject);
Assert.IsNull(object anObject, string message);
Assert.IsNull(object anObject, string message, object[] parameters);

The following code snippet shows how to use this comparison method:

[TestFixture]
public class SimpleClassTests
{
    [Test]
    public void TestSomething()
    {
        object anObject = null;
        Assert.IsNull(anObject, "anObject is not null!");
    }
}

Assert.IsNotNull()

This condition method tests that the condition parameter does not evaluate to null.

The following are all the method signatures available for this method:

Assert.IsNotNull(object anObject);
Assert.IsNotNull(object anObject, string message);
Assert.IsNotNull(object anObject, string message, object[] parameters);

The following code snippet shows how to use this comparison method:

[TestFixture]
public class SimpleClassTests
{
    [Test]
    public void TestSomething()
    {
        object anObject = null;
        Assert.IsNotNull(anObject, "anObject is null!");
    }
}

Assert.Fail()

This utility method allows you to generate test failure exceptions, often used when performing project-specific assertions.

The following are all the method signatures available for this method:

Assert.Fail()
Assert.Fail(string message)
Assert.Fail(string message, object[] parameters)

The following code snippet shows how to use this utility method:

[TestFixture]
public class SimpleClassTests
{
    [Test]
    public void TestSomething()
    {
        Assert.Fail("I was explicitly thrown!");
    }
}

Assert.Ignore()

This utility methods allows you to ignore a particular test during runtime, but should be used sparingly. A better approach is to use the [Category] attribute and run the test groups applicable at the time.

The following are all the method signatures available for this method:

Assert.Ignore()
Assert.Ignore(string message)
Assert.Ignore(string message, object[] parameters)

The following code snippet shows how to use this utility method:

[TestFixture]
public class SimpleClassTests
{
    [Test]
    public void TestSomething()
    {
        Assert.Fail("I was explicitly thrown!");
    }
}

A Simple Example

In order to perform testing, we obviously need a class to test against. Defined below is a simple class we can use for unit testing. As described by its name, the class is extremely simplistic, so I will just show the code for it and move right on to the creation of a test fixture.

using System;

namespace SimpleLibrary
{

    public class SimpleClass
    {
        private string _simpleProperty = "";

        public string SimpleProperty
        {
            get { return _simpleProperty; }
            set { _simpleProperty = value; }
        }

        public void BrokenMethod()
        {
            throw new ApplicationException("I am a faulty method! Fix me!");
        }

        public void GoodMethod()
        {
            throw new NotImplementedException();
        }

        public static int AddTwoNumbers(int left, int right)
        {
            return left - right;
          }
    }
}

Now that we have a simple class to test against, we need to create the test fixture that NUnit will use to coordinate the tests:

using System;
using NUnit.Framework;

namespace SimpleLibrary.UnitTesting
{

    [TestFixture]
    public class ExampleFixture
    {

        private SimpleLibrary.SimpleClass _simpleClass = null;

        [SetUp]
        public void SetUp()
        {
            _simpleClass = new SimpleLibrary.SimpleClass();
        }

        [TearDown]
        public void TearDown()
        {
            _simpleClass = null;
        }

        [Test]
        public void AddTwoNumbersTest()
        {
            int result = SimpleLibrary.SimpleClass.AddTwoNumbers(5, 7);
            Assert.AreEqual(12, result);
        }

      [Test]
      public void SimplePropertyTest()
      {
          Assert.AreEqual("", _simpleClass.SimpleProperty);
          _simpleClass.SimpleProperty = "This is a test";
          Assert.AreEqual("This is a test", _simpleClass.SimpleProperty);
      }

      [Test]
      public void ExplicitFailureTest()
      {
          _simpleClass.BrokenMethod();
      }

      [Test, Ignore("This is a deprecated test")]
      public void DeprecatedTest()
      {
          _simpleClass.GoodMethod();
      }

      [Test, ExpectedException(typeof(NotImplementedException))]
      public void GoodMethodTest()
      {
          _simpleClass.GoodMethod();
      }
   }
}

Notice that the above class has been marked with the [TestFixture] attribute. This is required so that NUnit can identify the test fixtures to run by searching all classes within the assembly that have this attribute type.

Running Tests

After the test fixture is created, it is time to begin testing. As mentioned previously, there are two interfaces for NUnit: console and GUI. This chapter will cover the GUI version. For now, we will explicitly launch the NUnit application, but later on, an alternative method is discussed that shows how to attach NUnit to the start event in Visual Studio. There is also an open source project called TestDriven.NET that offers enhanced .NET unit testing functionality, including integration support between Visual Studio and various unit testing frameworks like NUnit; definitely worth looking at.

Start by launching the NUnit GUI version, and you should be presented with a dialog similar to the one shown in Figure 10.3.

Screenshot of main NUnit interface.

Figure 10.3. Screenshot of main NUnit interface.

You will be prompted to save the NUnit project somewhere, and it is common to place the .nunit file in the same directory as the project file for the assembly being tested. After the project file is saved, you need to add the assemblies that contain your test fixtures. You can do this by selecting Project>Add Assembly from the main menu.

Figure 10.4 shows the NUnit interface after the example test fixture for this chapter has been added to it.

Screenshot of NUnit after adding a target assembly.

Figure 10.4. Screenshot of NUnit after adding a target assembly.

NUnit displays all the test assemblies and fixtures in a hierarchical fashion, and it executes tests in a similar way. Clicking the Run button will begin the tests, and all the tests below the currently selected node in the assembly tree will execute. This allows you to target all, some, or specific tests to run. You can group tests into categories as well.

Figure 10.5 shows the NUnit interface after the tests have been executed. You will notice that the output from errors and ignored tests appear in the tab group on the right, whereas successful tests are not as verbose. Successful tests appear green in the assembly tree, errors appear red, and ignored tests appear yellow.

Screenshot of NUnit after running the tests.

Figure 10.5. Screenshot of NUnit after running the tests.

Your assemblies may require a configuration file to function correctly, in which case it is important to note that NUnit creates a new AppDomain for each test assembly, so the configuration file must reside in the same directory as the assembly.

For example, SimpleLibrary.UnitTests.dll and SimpleLibrary.UnitTests.dll.config should be paired together in the same folder.

Debugging with Visual Studio

There may be times when you wish to debug the code while performing unit tests, and sometimes you may even want to debug the unit tests themselves. NUnit and Visual Studio both execute code in different AppDomains, so any breakpoints you set in the Visual Studio IDE will not fire when NUnit is run. There is a trick you can use to accomplish this, though. Visual Studio offers the ability to attach external programs to its debugging features when assemblies are consumed.

Open the property pages for the assembly you wish to consume externally, and navigate to the Debug page in the project properties. You should be presented with a dialog similar to the one shown in Figure 10.6.

Debugging property page.

Figure 10.6. Debugging property page.

Under Start Action, select the Start external program option, and set the field value to the file system path of either the GUI or command line executable of NUnit.

You must specify the assemblies to load in the command line arguments; otherwise, NUnit will launch with the last loaded project if there is one. Each assembly path should be separated by a space, and be sure to use double quotes around the path if spaces exist in it. You can alter the command line arguments to automatically start running your tests after NUnit opens. If you prefix the arguments line with /run and a space before the list of assemblies, NUnit will launch and immediately begin processing. Figure 10.7 shows the property page after it has been configured to be consumed externally.

Debugging property page configured.

Figure 10.7. Debugging property page configured.

At this point, you can have the test’s assembly selected as the startup project and run everything. If configured correctly, NUnit will fire up with your assembly loaded.

Conclusion

Unit testing is a worthwhile habit to pick up that is extremely beneficial on both a professional and personal software development level. This chapter covered a fair amount of unit testing and performing unit tests on the .NET platform, but the concept of unit testing is much more complex than what I have described here. There are many are topics like regression tests, integration tests, mock objects, and data-driven testing, which are beyond the scope of the information presented here.

Links to additional information and resources are listed below.

In addition to NUnit, there are a couple of other unit testing frameworks available for C#.NET, such as csUnit. Some versions of Visual Studio 2005 also have a unit testing framework built in, but this chapter was meant to focus on a solution that does not require a particular IDE version to work.

Use whichever framework you feel comfortable with; I chose NUnit because it works great for all my projects and is widely accepted throughout the .NET development community. I have used csUnit for other projects when its use is a requirement for the project, and transitioning between csUnit and NUnit is extremely easy. The attribute names are all the same, with the exception of varying support for FixtureSetUp and FixtureTearDown, and a comparable Assert class exists in both frameworks. There are a couple of naming differences between the Assert classes in both frameworks, but they are minor. The biggest difference is that you will need to reference the correct framework for csUnit and remove the reference to the NUnit framework.

Note

You can download NUnit at http://www.nunit.org.

 

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.104.27