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:

image

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.

image

6) The test project is then added to your Solution Explorer.

image

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!

Filed under: , , , ,

Comments

# Unit Testing: Execution

Sunday, October 25, 2009 5:50 PM by Deborah's Developer MindScape

Visual Studio 2008 (Professional Edition and above) provides a really nice set of tools for development

# Unit Testing: Code Coverage

Sunday, October 25, 2009 6:27 PM by Deborah's Developer MindScape

Visual Studio 2008 (Professional Edition and above) provides a really nice set of tools for development

# re: Unit Testing: An Introduction

Monday, October 26, 2009 4:33 AM by Anon

Hi there,

Isn't it bad practice to use the autogenerated unit tests.  I heard somewhere that it is.

I heard that its best to write the unit test from scratch.

# Interesting Finds: October 26, 2009

Monday, October 26, 2009 7:51 AM by Jason Haley

Interesting Finds: October 26, 2009

# re: Unit Testing: An Introduction

Monday, October 26, 2009 11:24 AM by Deborah Kurata

Hi Anon -

Thank you for dropping by my blog.

Visual Studio is not really generating your unit test ... only a template for your unit test. You still have to write all of the code to perform the test.

So in my opinion, there is no downside. Think of this generated code as your unit test snippet: you still need to fill in all of the important details.

Hope this helps clarify.

# Unit Testing: Testing Properties

Thursday, October 29, 2009 5:01 PM by Deborah's Developer MindScape

Visual Studio 2008 (Professional Edition and above) provides a really nice set of tools for development

# Unit Testing: Exposing Internal/Friend Members

Thursday, October 29, 2009 5:23 PM by Deborah's Developer MindScape

Visual Studio 2008 (Professional Edition and above) provides a really nice set of tools for development

# Unit Testing: Exposing Private Members

Thursday, October 29, 2009 6:19 PM by Deborah's Developer MindScape

Visual Studio 2008 (Professional Edition and above) provides a really nice set of tools for development

# Unit Testing: An Introduction - Deborah Kurata

Sunday, November 01, 2009 8:35 AM by DotNetShoutout

Thank you for submitting this cool story - Trackback from DotNetShoutout

# credit restoration

Tuesday, November 10, 2009 10:42 PM by credit restoration

I just stopped by to check out your site.

# re: Unit Testing: An Introduction

Tuesday, November 17, 2009 1:15 PM by Roger Pearse

Well, try running debug on some of these generated tests.  You can't step into the code under test!

# re: Unit Testing: An Introduction

Tuesday, November 17, 2009 3:08 PM by Deborah Kurata

I have a *very large* solution and I am able to debug from the tests with no problem.

However, if I have been debugging from within the tests for a significant amount of time (like all afternoon), I then find that I have difficulty. Often just existing Visual Studio and reopening the solution solves the issues and allows me to continue.

Hope this helps.

# re: Unit Testing: An Introduction

Wednesday, February 01, 2012 5:52 AM by Crickett

Deborah what a joy it is to read a techy web by a woman in simple English, easy to understand, will try my first unit test today thanks cricket

Leave a Comment

(required) 
(required) 
(optional)
(required) 
If you can't read this number refresh your screen
Enter the numbers above: