Deborah's Developer MindScape






         Tips and Techniques for Web and .NET developers.

October 29, 2009

Unit Testing: Testing Properties

Filed under: C#,Testing,VB.NET @ 5:01 pm

Visual Studio 2008 (Professional Edition and above) provides a really nice set of tools for development and execution of unit tests. This post provides suggestions for testing your property getters and setters.

[To begin with an overview of unit testing, start here.]

Let’s say your Customer class has a LastName property that looks like this:

In C#:

    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);
                }
            }
        }
    }

(Someone just mentioned how all of those ending braces look like a flock of sea gulls! LOL!)

In VB:

    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

NOTE: The SetEntityState method used in this code is coming from the business object base class provided in this prior post.

The key step for developing a good unit test is to define the test scenarios.

Looking at the requirements, the following testing scenarios are required for the LastName property:

  1. Initial null value; set to null value (should perform validation but not set the dirty flag)
  2. Initial null value; set to valid  string (should perform validation and set the dirty flag)
  3. Initial null value; set to empty string (should perform validation and set the dirty flag)
  4. Initial string value; set to null value (should perform validation and set the dirty flag)
  5. Initial string value, set to different string value (should perform validation and set the dirty flag)
  6. Initial string value, set  to same string value (should not  perform validation and not set the dirty flag)
  7. 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.

If you are building the unit test from existing code, you can generate the basic structure of the unit test for the property following the techniques detailed in this prior post.

But the test generation only gives you the basic template for testing your properties. This post focuses on how to update that template to perform the required unit testing.

Visual Studio will generate the following unit test template for the LastName property that was shown at the beginning of this post:

In C#:

/// <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:

”'<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

To cover the defined testing scenarios, this code needs to be enhanced as follows:

In C#:

/// <summary>
///A test for LastName
///</summary>
[TestMethod()]
public void LastNameTest()
{
    Customer target;
    string expected;
    string actual;

     // Null to Null
    target = new Customer();
    expected = null;
    target.LastName = expected;
    actual = target.LastName;
    Assert.AreEqual(expected, actual, "Values are not equal");
    Assert.AreEqual(false, target.IsDirty,
                              
"Object not marked as dirty");

    // Null to value
    target = new Customer();
    expected = "Johnson";
    target.LastName = expected;
    actual = target.LastName;
    Assert.AreEqual(expected, actual, "Values are not equal");
    Assert.AreEqual(true, target.IsDirty, _
                                "Object not marked as dirty");

    // Null to Empty
    target = new Customer();
    expected = string.Empty;
    target.LastName = expected;
    actual = target.LastName;
    Assert.AreEqual(expected, actual, "Values are not equal");
    Assert.AreEqual(true, target.IsDirty,
                                "Object not marked as dirty");

    // Value to Null
    target = new Customer() {LastName = "Johnson"};
    target.SetEntityState(BoBase.EntityStateType.Unchanged);
    expected = null;
    target.LastName = expected;
    actual = target.LastName;
    Assert.AreEqual(expected, actual, "Values are not equal");
    Assert.AreEqual(true, target.IsDirty,
                                "Object not marked as dirty");

    // Value to new Value
    target = new Customer() {LastName = "Johnson"};
    target.SetEntityState(BoBase.EntityStateType.Unchanged);
    expected = "Jones";
    target.LastName = expected;
    actual = target.LastName;
    Assert.AreEqual(expected, actual, "Values are not equal");
    Assert.AreEqual(true, target.IsDirty,
                                "Object not marked as dirty");

    // Value to same Value
    target = new Customer() {LastName = "Johnson"};
    target.SetEntityState(BoBase.EntityStateType.Unchanged);
    expected = "Johnson";
    target.LastName = expected;
    actual = target.LastName;
    Assert.AreEqual(expected, actual, "Values are not equal");
    Assert.AreEqual(false, target.IsDirty,
                                "Object not marked as dirty");

    // Value to Empty
    target = new Customer() {LastName = "Johnson"};
    target.SetEntityState(BoBase.EntityStateType.Unchanged);
    expected = string.Empty;
    target.LastName = expected;
    actual = target.LastName;
    Assert.AreEqual(expected, actual, "Values are not equal");
    Assert.AreEqual(true, target.IsDirty, 
                                "Object not marked as dirty");

}

In VB:

”'<summary>
”’A test for LastName
”'</summary>
<TestMethod()> _
Public Sub LastNameTest()
    Dim target As Customer
    Dim expected As String
    Dim actual As String

    ‘ Null to Null
    target = New Customer
    expected = Nothing
    target.LastName = expected
    actual = target.LastName
    Assert.AreEqual(expected, actual, "Values are not equal")
    Assert.AreEqual(False, target.IsDirty, _
                                "Object not marked as dirty")

    ‘ Null to value
    target = New Customer
    expected = "Johnson"
    target.LastName = expected
    actual = target.LastName
    Assert.AreEqual(expected, actual, "Values are not equal")
    Assert.AreEqual(True, target.IsDirty, _
                                "Object not marked as dirty")

    ‘ Null to Empty
    target = New Customer
    expected = String.Empty
    target.LastName = expected
    actual = target.LastName
    Assert.AreEqual(expected, actual, "Values are not equal")
    Assert.AreEqual(True, target.IsDirty, _
                                "Object not marked as dirty")

    ‘ Value to Null
    target = New Customer With {.LastName = "Johnson"}
    target.SetEntityState(BOBase.EntityStateType.Unchanged)
    expected = Nothing
    target.LastName = expected
    actual = target.LastName
    Assert.AreEqual(expected, actual, "Values are not equal")
    Assert.AreEqual(True, target.IsDirty, _
                                "Object not marked as dirty")

    ‘ Value to new Value
    target = New Customer With {.LastName = "Johnson"}
    target.SetEntityState(BOBase.EntityStateType.Unchanged)
    expected = "Jones"
    target.LastName = expected
    actual = target.LastName
    Assert.AreEqual(expected, actual, "Values are not equal")
    Assert.AreEqual(True, target.IsDirty, _
                                "Object not marked as dirty")

    ‘ Value to same Value
    target = New Customer With {.LastName = "Johnson"}
    target.SetEntityState(BOBase.EntityStateType.Unchanged)
    expected = "Johnson"
    target.LastName = expected
    actual = target.LastName
    Assert.AreEqual(expected, actual, "Values are not equal")
    Assert.AreEqual(False, target.IsDirty, _
                               "Object not marked as dirty")

    ‘ Value to Empty
    target = New Customer With {.LastName = "Johnson"}
    expected = String.Empty
    target.LastName = expected
    actual = target.LastName
    Assert.AreEqual(expected, actual, "Values are not equal")
    Assert.AreEqual(True, target.IsDirty, _
                                "Object not marked as dirty")

End Sub

NOTE: The SetEntityState method and IsDirty property used in this code are coming from the business object base class provided in this prior post.

NOTE: You may get an error when calling SetEntityState because in the business object base class is defined to be protected internal (protected friend in VB), not public. If so, you need to use the technique presented here to allow your tests to access internal/friend properties and methods.

WOW! That is a LOT of test code! Some experts have said that every line of code needs at least 3 – 5 lines of test code.

We have 17 lines of C# code and 15 lines of VB code in our property procedure. If I counted correctly, there are 51 lines of C# test code and 50 lines  of VB test code. So that is about 3x the number of code lines.

Let’s walk through what this test code is doing, following through with the testing scenarios provided earlier in this post.

Scenario 1: The code creates a new instance of the Customer class. By default, a new instance sets any string values to null/nothing. So the test code simply sets the property to null/nothing, gets the value, confirms that the value is as expected, and ensures that it did not get marked as dirty.

Scenario 2: The code again creates a new instance of the Customer class. By default a new instance sets any string values to null/nothing. So the test code sets the property to a value, gets the value, confirms that the value is as expected, and ensures that the object was marked as dirty.

Scenario 3: The code again creates a new instance of the Customer class. By default a new instance sets any string values to null/nothing. So the test code sets the property to an empty string, gets the value, confirms that the value is as expected, and ensures that the object was marked as dirty.

Now for the harder scenarios. There is no easy way to set a non-null initial value for a property. One option is to set the private backing variable to the desired initial value. But with the backing variable being private, there is extra code to write to make it work.

Another option is to set the property using the setter. But this marks  the object as dirty, adversely interfering with the test. So if you use this technique, you then need to call SetEntityState to clear the entity state and ensure it is not marked as dirty. The sample code used this technique.

Scenario 4: The code creates a new instance of the customer class, setting the initial value of the property to a valid value. It then calls SetEntityState to clear the dirty flag. Then the test code sets the property to a null, gets the value, confirms that the value is as expected, and ensures that the object was marked as dirty.

Scenario 5: The code creates a new instance of the customer class, setting the initial value of the property to a valid value. It then calls SetEntityState to clear the dirty flag. Then the test code sets the property to another valid value, gets the value, confirms that the value is as expected, and ensures that the object was marked as dirty.

Scenario 6: The code creates a new instance of the customer class, setting the initial value of the property to a valid value. It then calls SetEntityState to clear the dirty flag. Then the test code sets the property to the same value, gets the value, confirms that the value is as expected, and ensures that the object was not marked as dirty.

Scenario 7: The code creates a new instance of the customer class, setting the initial value of the property to a valid value. It then calls SetEntityState to clear the dirty flag. Then the test code sets the property to an empty string, gets the value, confirms that the value is as expected, and ensures that the object was marked as dirty.

If the field was validated, such as required field validation or a maximum length check, additional scenarios and associated test code would be required.

So for every business object property in your application, define the appropriate set of test scenarios and build the test code to support each scenario.

Or build a test base class that performs the basic set of tests for each of your properties. But that is left for a future post.

Enjoy!

2 Comments

  1.   Christophe Lambrechts — November 7, 2011 @ 6:39 am    Reply

    Shouldn’t you use something as a Data driven Unit Test for this kind of situation. MSDN reference: http://msdn.microsoft.com/en-us/library/ms182527.aspx

    Or at least I would advice to use a kind of function, you have 7 times almost the same code in your file. This is not a very good programming practice.

  2.   DeborahK — November 7, 2011 @ 1:09 pm    Reply

    Hi Christophe –

    Yes, in our *real* applications we have a function that handles this. (See the last paragraph of the post.)

    The purpose of this post was to think through how a property could be unit tested to give the reader an idea of the thought process and the types of things to check. The post did not include the “refactored” code showing a base class or set of general functions.

    Thanks for your comments!

RSS feed for comments on this post. TrackBack URI

Leave a comment

© 2023 Deborah's Developer MindScape   Provided by WPMU DEV -The WordPress Experts   Hosted by Microsoft MVPs