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;
namespace example {
[TestFixture]
public class FooTests() {
public FooTests() {
}
}
}
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 {
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;
namespace example {
[TestFixture]
public class FooTests() {
public FooTests() {
}
[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 {
[TestFixture]
public class FooTests() {
[SetUp]
public void SetUp() {
_myTestData = new MyClass();
}
[TearDown]
public void CleanUp() {
_myTestData = null;
}
[Test]
public void MyFirstTest() {
Assert.Equals(0, _myTestData.Value);
_myTestData.Value = 25;
Assert.Equals(25, _myTestData.Value);
}
[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 {
[TestFixture]
public class FooTests() {
[Test]
public void MyFirstTest() {
}
[Test, Ignore("Implementation not complete yet.")]
public void SkipThisOne() {
}
}
}
using System;
using csUnit;
namespace example {
[TestFixture]
[Ignore("Fixture is of no use yet")]
public class FooTests() {
[Test]
public void MyFirstTest() {
}
}
}
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 {
[TestFixture]
public class FooTests() {
[Test]
[ExpectedException(typeof((MyProject.MyException))]
public void MyFirstTest() {
throw(new MyException("This is a test"));
}
[Test]
public void MyFirstTest() {
try {
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! |