Unit Testing: An Introduction
Posted
Sun, Oct 25 2009 14:04
by
Deborah Kurata
If you have Visual Studio 2008 or later and have the Professional Edition or better (NOT the Express Editions), you have some very nice unit testing tools within your Visual Studio environment. These tools help you write, execute, and track your unit tests and code coverage. This post provides an introduction to using these tools.
If you are new to unit testing, the idea is to test the smallest possible units of your code. In most cases, the smallest units of code are the property procedures and methods of your classes.
For more general information about the purpose of unit testing, see this link.
NOTE: Unit testing is not meant to replace integration testing, system testing, or user testing.
In some development methodologies, such as Test Driven Development (TDD), unit tests are written before the code is written. You capture the basic requirements in the unit test and then write the code. The code is complete when the unit tests pass. Visual Studio 2010 has features to assist with this "unit test first" approach.
But this introduction demonstrates how to write unit tests for existing code. This technique is helpful if you follow a code-first approach or if you received code from another source or have old code that did not originally have unit tests.
Build the Code
The business object tested in this post is a simplified Customer class that uses the business object base class defined in this post.
The Customer class is shown below.
In C#:
public class Customer: BoBase
{
private int? _CustomerId;
public int? CustomerId
{
get { return _CustomerId; }
internal set
{
if (_CustomerId == null || !_CustomerId.Equals(value))
{
string propertyName = "CustomerId";
// Perform any validation here
if (!_CustomerId.Equals(value))
{
_CustomerId = value;
SetEntityState(EntityStateType.Modified,
propertyName);
}
}
}
}
private string _LastName;
public string LastName
{
get { return _LastName; }
set
{
if (_LastName == null || _LastName != value)
{
string propertyName = "LastName";
// Perform any validation here
if (_LastName != value)
{
_LastName = value;
SetEntityState(EntityStateType.Modified,
propertyName);
}
}
}
}
private string _FirstName;
public string FirstName
{
get { return _FirstName; }
set
{
if (_FirstName == null || _FirstName != value)
{
string propertyName = "FirstName";
// Perform any validation here
if (_FirstName != value)
{
_FirstName = value;
SetEntityState(EntityStateType.Modified,
propertyName);
}
}
}
}
private string _EmailAddress;
public string EmailAddress
{
get { return _EmailAddress; }
set
{
if (_EmailAddress == null || _EmailAddress != value)
{
string propertyName = "EmailAddress";
// Perform any validation here
if (_EmailAddress != value)
{
_EmailAddress = value;
SetEntityState(EntityStateType.Modified,
propertyName);
}
}
}
}
}
In VB:
Public Class Customer
Inherits BOBase
Private _CustomerId As Integer?
Public Property CustomerId() As Integer?
Get
Return _CustomerId
End Get
Friend Set(ByVal value As Integer?)
If _CustomerId Is Nothing OrElse _
Not _CustomerId.Equals(value) Then
Dim propertyName As String = "CustomerId"
' Perform any validation
If Not _CustomerId.Equals(value) Then
_CustomerId = value
SetEntityState(EntityStateType.Modified, propertyName)
End If
End If
End Set
End Property
Private _FirstName As String
Public Property FirstName() As String
Get
Return _FirstName
End Get
Set(ByVal value As String)
If _FirstName Is Nothing OrElse _
_FirstName IsNot value Then
Dim propertyName As String = "FirstName"
' Perform any validation
If _FirstName IsNot value Then
_FirstName = value
SetEntityState(EntityStateType.Modified, _
propertyName)
End If
End If
End Set
End Property
Private _LastName As String
Public Property LastName() As String
Get
Return _LastName
End Get
Set(ByVal value As String)
If _LastName Is Nothing OrElse _
_LastName IsNot value Then
Dim propertyName As String = "LastName"
' Perform any validation
If _LastName IsNot value Then
_LastName = value
SetEntityState(EntityStateType.Modified, _
propertyName)
End If
End If
End Set
End Property
Private _EmailAddress As String
Public Property EmailAddress() As String
Get
Return _EmailAddress
End Get
Set(ByVal value As String)
If _EmailAddress Is Nothing OrElse _
_EmailAddress IsNot value Then
Dim propertyName As String = "EmailAddress"
' Perform any validation
If _EmailAddress IsNot value Then
_EmailAddress = value
SetEntityState(EntityStateType.Modified, _
propertyName)
End If
End If
End Set
End Property
End Class
The first if statement in each of the property procedures for the Customer class checks whether the current value is null or was changed. If the property is changed, you want to revalidate it, set it as modified, and generate a PropertyChanged event (which is handled in the SetEntityState method).
NOTE: C# uses a != operator to judge whether the value was changed. VB cannot use the <> operator because VB propagates null values. So if either value is null (nothing) the result is false. (See this msdn entry for more information.) To prevent this problem, IsNot is used in the VB code to determine if the value was changed.
The null check allows for possible validation of required fields (that is, fields that cannot be null or empty). If you know you will never need a null validation check, you can leave the null check off the first if statement.
The propertyName variable defines a name that you can use in any validation error messages and it is the name used in the PropertyChanged event.
You can then perform any necessary validation. In this simple example, no validation was added. If you want to see an example of some validation, check out this post.
Finally, if the value was changed, the backing variable is set to the new value and the SetEntityState method is called to mark the business object as "dirty" and to generate the PropertyChanged event.
Define the Testing Scenarios
So let's start with the test of the LastName property. Looking at the requirements, the following testing scenarios are required:
- Initial null value; set to null value (should perform validation but not set the dirty flag)
- Initial null value; set to valid string (should perform validation and set the dirty flag)
- Initial null value; set to empty string (should perform validation and set the dirty flag)
- Initial string value; set to null value (should perform validation and set the dirty flag)
- Initial string value, set to different string value (should perform validation and set the dirty flag)
- Initial string value, set to same string value (should not perform validation and not set the dirty flag)
- Initial string value, set to empty value (should perform validation and set the dirty flag)
And if you have validation code, you will have more scenarios to test valid and invalid values. But this is enough to give you the general idea.
So now let's generate the unit test for the LastName property using the tools provided in Visual Studio (Professional Edition or above).
Generate the Unit Test
The following are the steps for generating the unit test for a particular property procedure or method:
1) Open the Code Editor for the code you want to test.
2) Right-click and select Create Unit Tests... from the context menu.
The following dialog will appear:
3) Select the properties, methods, or constructors you wish to test.
If you were in a specific property procedure or method, that procedure or method is automatically checked in this dialog. You can select to generate tests for any properties, methods, or constructors using this dialog.
Notice the Output project combobox on this dialog. Using this combobox, you can create a new C# unit test, new VB unit test, or select any existing unit test project if you already have some in your solution. This allows you to add unit tests to existing unit testing projects at any point in the development process.
4) Click OK.
5) If you are creating a new C# or VB project, enter the project name and click Create.
6) The test project is then added to your Solution Explorer.
If you created a VB test project, the result is the same, but with a CustomerTest.vb file within the BoTest project.
View the Generated Code
Visual Studio automatically creates an first cut of your unit testing code.
In C#:
Double-click on CustomerTest.cs to view the code.
/// <summary>
///A test for LastName
///</summary>
[TestMethod()]
public void LastNameTest()
{
Customer target = new Customer(); // TODO: Initialize to an appropriate value
string expected = string.Empty; // TODO: Initialize to an appropriate value
string actual;
target.LastName = expected;
actual = target.LastName;
Assert.AreEqual(expected, actual);
Assert.Inconclusive("Verify the correctness of this test method.");
}
In VB:
Double-click on CustomerTest.vb to view the code.
'''<summary>
'''A test for LastName
'''</summary>
<TestMethod()> _
Public Sub LastNameTest()
Dim target As Customer = New Customer ' TODO: Initialize to an appropriate value
Dim expected As String = String.Empty ' TODO: Initialize to an appropriate value
Dim actual As String
target.LastName = expected
actual = target.LastName
Assert.AreEqual(expected, actual)
Assert.Inconclusive("Verify the correctness of this test method.")
End Sub
The TestMethod attribute defines this method as a unit test. It is then picked up by the other unit testing tools within Visual Studio.
The generated code includes some ToDo notes, defining where you should change the code.
The Assert class is a key aspect of your unit tests. It provides many methods that you can use to assert whether your test produced expected results.
The generated code uses two of the Assert class methods. The AreEqual method determines whether the expected and actual values are equal. If not, it fails the test.
The Inconclusive method always causes the test to fail. This is added to all generated code to ensure that the test will fail until you modify the test with valid values.
At this point, you can try to execute this unit test, or update it first and then execute it.
See the following posts for more information:
Enjoy!