Home Download Documentation Getting Started Support Other Versions

Unit Testing With C#

Introduction - Programming Language - Prerequisites - Getting Started - Test Methods - Unit Test Lifecycle - Ignoring A Test Or A Test Fixture - Expected Exceptions - Related Topics - Credits

by Jake Anderson

Introduction

In this document you will learn about creating testing classes for your project. One of the hardest tasks when using a new tool is learning how to integrate it into your regular build lifecycle. Hopefully this tutorial will help you in creating new test classes, maintain your existing test classes, and take advantage of advanced features in the csUnit API. If you have comments about the methodologies described in this tutorial, please send them to either the csUnit discussion group or to the csUnit project team.

Happy Testing!

Programming Language

The tutorial uses C#.

Prerequisites

This tutorial makes the following assumptions:

  • Visual Studio .NET and csUnit 1.8.8 or later are installed.

Getting Started

The first thing you will be writing is a TestFixture . This is accomplished by creating a class and assigning it the [TestFixture] attribute. This is demonstrated in the following code.

using System;
using csUnit;
// Don't forget to add the csUnit.dll reference to your testing project

namespace example {
    /// <summary>
    /// Summary description for FooTests.
    /// </summary>
    [TestFixture]
    public class FooTests() {
       public FooTests() {
          //
          // TODO: Add constructor logic here
          //
       }
    }
}

The [TestFixture] attribute takes no parameters and can be applied to classes only. The purpose of the [TestFixture] attribute is to identify which classes will be running test cases. Upon loading your test assembly, csUnit searches the entire assembly for classes with this custom attribute set. Don't forget to add a reference to the csUnit assembly to your projects references, and also add the using directive to the source code (see sample above).

NOTE - The [TestFixture] attribute is passed down in the inheritance chain for test classes in csUnit 1.8.5. This means you can create a base class that is a [TestFixture], and that attribute will pass down to all of its specializers.

The next step is for you to create test methods that will run the actual unit tests for your project. This is where you will need to select a testing methodology that best suits your style and project requirements. For this tutorial, a single testing method will be used to test a single method of a tested class. This is a very granular methodology that is commonplace amongst the automatic test-case generation tools.

Creating Test Methods

The [TestFixture] contains one of more methods that are flagged with the [Test] attribute. This attribute tells the framework that a particular method in the fixture is to be run during the unit testing phase. These method should be mutually exclusive of their state (a common practice) and should be designed to not have any side-effects (left-over state after the method terminates). If you leave side-effects, then subsequent test methods may be adversely affected by the leftover state.

In this example, let's use a simple class shown in the following code. We will be creating test methods to test an instance of this class. Our test methods will be verifying the set and get methods of the Value property.

using System;

namespace MyProject {
    /// <summary>
    /// This is the class to be tested..
    /// </summary>
    
    public class MyClass() {
       private int _ival = 0;
       public void MyClass() {
          _ival = 25;
       }
       public int Value
       {
         get
         {
           return _ival;
         }
         set
         {
           _ival = value;
         }
       }
    }
}

Now let's extend our test class to include a testing method, as shown in the following code.

using System;
using csUnit;
using MyProject;
// MyProject is where the class to be tested exists.

namespace example {
    /// <summary>
    /// Summary description for FooTests.
    /// </summary>
    [TestFixture]
    public class FooTests() {
       public FooTests() {
          // nothing to do here
       }
       /// <summary>
       /// Test the set and get methods of the MyClass.Value property
       /// </summary>
       [Test]
       public void TestValueProperty() {
         MyClass obj = new MyClass();
         Assert.Equals(0, obj.Value);
         obj.Value = 25;
         Assert.Equals(25, obj.Value);
       }
    }
}
As you can see, creating the test methods for your unit testing class can be very simple. With more complex classes, though, your test methods will become more complex. To that end, csUnit allows you to manage the lifecycle of a unit test by creating SetUp and TearDown methods. These are explained in the next section.

For more information on the Assert class, see the online documentation.

Unit Test Lifecycle

The [SetUp] and [TearDown] attributes are used to identify the methods that will 'SetUp' and 'TearDown' the testing state for a unit test. The testing state can be anything that you define, such as a commonly used bunch of classes that together create a mock context for your application. These methods, defined as such, will be run before and after the execution of each and every Test method in a [TestFixture].

The following is an example of using these two attributes in a [TestFixture]:

using System;
using csUnit;
using MyProject;

namespace example {
    /// <summary>
    /// Summary description for FooTests.
    /// </summary>
    [TestFixture]
    public class FooTests() {
       [SetUp]
       public void SetUp() {
          _myTestData = new MyClass();
       }

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

       /// <summary>
       /// Test the set and get methods of the MyClass.Value property. 
       /// Note the change of state on the shared MyClass instance.  It 
       /// is important to TearDown this testing context so that subsequent
       /// tests do not see the changed state.
       /// </summary>
       [Test]
       public void MyFirstTest() {
         Assert.Equals(0, _myTestData.Value);
         _myTestData.Value = 25;
         Assert.Equals(25, _myTestData.Value);
       }
       /// <summary>
       /// A second test to verify that our TearDown and SetUp methods
       /// are cleaning up the residual state between unit tests.  If
       /// TearDown was not doing its job, then eith MyFirstTest() or
       /// MySecondTest() would fail on the first Assert because the 
       /// residual 'Value' would carry over to the next unit test.
       /// </summary>
       [Test]
       public void MySecondTest() {
         Assert.Equals(0, _myTestData.Value);
         _myTestData.Value = 99;
         Assert.Equals(99, _myTestData.Value);
       }

       private MyClass _myTestData = null;
    }
}

Again, the signature of both, the SetUp and the TearDown methods must be public void just as those test methods were defined.

Ignoring A Test Or A TestFixture

Sometimes when a unit test is not ready for testing, it is good to ignore that test while still being able to run the other tests. To that end, csUnit allows you to set an [Ignore ] attribute on a test method or even class. Just as the name would imply, this attribute will cause the method or class to be ignored when the unit tests are run.

The following is an example use of the [Ignore] attribute:

using System;
using csUnit;

namespace example {
    /// <summary>
    /// Summary description for FooTests.
    /// </summary>
    [TestFixture]
    public class FooTests() {
       [Test]
       public void MyFirstTest() {
          // your test implementation goes here
       }

       [Test, Ignore("Implementation not complete yet.")]
       public void SkipThisOne() {
       }
    }
}
using System;
using csUnit;

namespace example {
    /// <summary>
    /// Summary description for FooTests.
    /// </summary>
    [TestFixture]
    [Ignore("Fixture is of no use yet")]
    public class FooTests() {
       [Test]
       public void MyFirstTest() {
          // your test implementation goes here
       }
    }
}

If the Ignore attribute is put on a test fixture, it will not even be instantiated during running the tests. csUnit accesses only the type information including the custom attributes for the test fixture and the tests contained in it.

Expected Exceptions

If you are testing a feature that throws exceptions, and you don't want to add try/catch blocks all over in your testing code, then you can use the [ExpectedException] attribute to keep your code simple and still functional. The [ExpectedException] attribute tells the framework that a specific exception is going to be thrown by a test method, and that it should just ignore it when it happens. This does NOT tell the framework that if an expected exception is not thrown, then mark the test as failed. You will have to hand-code that logic.

using System;
using csUnit;
using MyProject;

namespace example {
    /// <summary>
    /// Shows use of [ExpectedException].
    /// </summary>
    [TestFixture]
    public class FooTests() {
       
       /// <summary>
       /// This test throws the MyException exception and lets the framework handle it.
       /// </summary>
       
       [Test]
       [ExpectedException(typeof((MyProject.MyException))]
       public void MyFirstTest() {
          // Throw MyException to test this out
          throw(new MyException("This is a test"));
       }
       
       /// <summary>
       /// This is a negative-test where a test would cause an exception and mark
       /// test as 'failed' if the exception is not thrown.
       /// </summary>
       
       [Test]
       public void MyFirstTest() {
          // Throw MyException to test this out
          try {
            // Do something here
            Assert.Fail("Expected MyException to be thrown.");
          }
          catch(MyException ex) {
          }
       }
    }
}

Related Topics

Credits

Portions of this document were taken from other tutorials written by Manfred Lange.

Happy Testing!

Sponsors:

Extreme Simplicity Logo

Agile Utilities Logo


Sources hosted by

Get csUnit - unit testing for .NET at SourceForge.net. Fast, secure and Free Open Source software downloads



Copyright © 2002-2009 by Agile Utilities NZ Ltd. All rights reserved. Site design by Andreas Weiss. This site is protected by bot traps.