recognizes this as a test project and automatically analyzes it for any test cases in order to populate
the various test windows.
Classes and methods used in the testing process are marked with an appropriate attribute. The
attributes are used by the testing engine to enumerate all the test cases within a particular assembly.
TestClass
All test cases must reside within a test class that is appropriately marked with the TestClass
attribute. Although it may appear that there is no reason for this attribute other than to align test
cases with the class and member that they are testing, you will later see some benefits associated
with grouping test cases using a test class. In the case of testing the Subscription class, a test class
called SubscriptionTest was created and marked with the TestClass attribute. Because Visual
Studio uses attributes to locate classes that contain test cases, the name of this class is irrelevant.
However, adopting a naming convention, such as adding the Test suffix to the class being tested,
makes it easier to manage a large number of test cases.
TestMethod
Individual test cases are marked with the TestMethod attribute, which is used by Visual Studio
to enumerate the list of tests that can be executed. The CurrentStatusTest method in the
SubscriptionTest class is marked with the TestMethod attribute. Again, the actual name of
this method is irrelevant, because Visual Studio only uses the attributes. However, the method
name is used in the various test windows when the test cases are listed, so it is useful for test
methods to have meaningful names. This is especially true when reviewing test results.
additional test attributes
As you have seen, the unit-testing subsystem within Visual Studio uses attributes to identify test
cases. A number of additional properties can be set to provide further information about a test case.
This information is then accessible either via the Properties window associated with a test case or
within the other test windows. This section goes through the descriptive attributes that can be applied
to a test method.
Description
Because test cases are listed by test method name, a number of tests may have similar names, or
names that are not descriptive enough to indicate what functionality they test. The Description
attribute, which takes a String as its sole argument, can be applied to a test method to provide
additional information about a test case.
Your first Test Case .
201
owner
The Owner attribute, which also takes a String argument, is useful for indicating who owns, wrote,
or is currently working on a particular test case.
Priority
The Priority attribute, which takes an Integer argument, can be applied to a test case to indicate
the relative importance of a test case. Though the testing framework does not use this attribute, it is
useful for prioritizing test cases when you are determining the order in which failing, or incomplete,
test cases are resolved.
Test Categories
The TestCategory attribute accepts a single String identifying one user-defined category for
the test. Like the Priority attribute the TestCategory attribute is essentially ignored by Visual
Studio but is useful for sorting and grouping related items together. A test case may belong to many
categories but must have a separate attribute for each one.
Work items
The WorkItem attribute can be used to link a test case to one or more work items in a work-itemtracking
system such as Team Foundation Server. If you apply one or more WorkItem attributes to a
test case, you can review the test case when making changes to existing functionality. You can read
more about Team Foundation Server in Chapter 57.
ignore
It is possible to temporarily prevent a test method from running by applying the Ignore attribute to
it. Test methods with the Ignore attribute will not be run and will not show up in the results list of
a test run.
You can apply the Ignore attribute to a test class as well to switch off all of the
test methods within it.
Timeout
A test case can fail for any number of reasons. A performance test, for example, might require
a particular functionality to complete within a specified time frame. Instead of the tester having
to write complex multi-threading tests that stop the test case once a particular timeout has been
reached, you can apply the Timeout attribute to a test case with a timeout value in milliseconds, as
shown in the following code. This ensures that the test case fails if that timeout is reached.
202 .
chaPter 11 uniT TeSTing
Vb
<TestMethod()>
<Owner(“Mike Minutillo”)>
<Description(“Tests the functionality of the Current Status Property”)>
<Priority(3)>
<Timeout(10000)>
<TestCategory(“Financial”)>
Public Sub CurrentStatusTest()
Dim target As Subscription = New Subscription
Dim actual As Subscription.Status
actual = target.CurrentStatus
Assert.AreEqual(Subscription.Status.Temporary, actual, _
“Subscription.CurrentStatus was not set correctly.”)
End Sub
Code snippet SubscriptionTests\SubscriptionTest.vb
c#
[TestMethod()]
[Owner(“Mike Minutillo”)]
[Description(“Tests the functionality of the Current Status Method”)]
[Priority(3)]
[Timeout(10000)]
[TestCategory(“Financial”)]
public void CurrentStatusTest()
{
Subscription target = new Subscription();
Subscription.Status actual;
actual = target.CurrentStatus;
Assert.AreEqual(Subscription.Status.Temporary, actual,
“Subscription.CurrentStatus was not set correctly.”);
}
Code snippet SubscriptionTests\SubscriptionTest.cs
This snippet augments the original CurrentStatusTest method with some of these attributes to
illustrate their usage. In addition to providing additional information about what the test case does
and who wrote it, this code assigns the test case a priority of 3 and a category of “Financial”.
Lastly, the code indicates that this test case should fail if it takes more than 10 seconds (10,000
milliseconds) to execute.
assertinG the facts
So far, this chapter has examined the structure of the test environment and how test cases are
nested within test classes in a test project. What remains is to look at the body of the test case and
review how test cases either pass or fail. (When a test case is generated, you saw that an Assert
.Inconclusive statement is added to the end of the test to indicate that it is incomplete.)
asserting the facts .
203
The idea behind unit testing is that you start with the system, component, or object in a known
state, and then run a method, modify a property, or trigger an event. The testing phase comes at
the end, when you need to validate that the system, component, or object is in the correct state.
Alternatively, you may need to validate that the correct output was returned from a method or
property. You do this by attempting to assert a particular condition. If this condition is not true, the
testing system reports this result and ends the test case. A condition is asserted, not surprisingly, via
the Assert class. There is also a StringAssert class and a CollectionAssert class, which provide
additional assertions for dealing with String objects and collections of objects, respectively.
the assert class
The Assert class in the UnitTesting namespace, not to be confused with the Debug.Assert or
Trace.Assert method in the System.Diagnostics namespace, is the primary class used to make
assertions about a test case. The basic assertion has the following format:
Vb
Assert.IsTrue(variableToTest, “Output message if this fails”)
c#
Assert.IsTrue(variableToTest, “Output message if this fails”);
As you can imagine, the first argument is the condition to be tested. If this is true, the test case
continues operation. However, if it fails, the output message is emitted and the test case exits with a
failed result.
This statement has multiple overloads whereby the output message can be omitted or String
formatting parameters supplied. Because quite often you won’t be testing a single positive condition,
several additional methods simplify making assertions within a test case:
.
IsFalse: Tests for a negative, or false, condition
.
AreEqual: Tests whether two arguments have the same value
.
AreSame: Tests whether two arguments refer to the same object
.
IsInstanceOfType: Tests whether an argument is an instance of a particular type
.
IsNull: Tests whether an argument is nothing
This list is not exhaustive — several more methods exist, including negative equivalents of those
listed. Also, many of these methods have overloads that allow them to be invoked in several
different ways.
the stringassert class
The StringAssert class does not provide any additional functionality that cannot be achieved with
one or more assertions via the Assert class. However, it not only simplifies the test case code by
making it clear that String assertions are being made; it also reduces the mundane tasks associated
with testing for particular conditions. The additional assertions are as follows:
204 .
chaPter 11 uniT TeSTing
.
Contains: Tests whether a String contains another String
.
DoesNotMatch: Tests whether a String does not match a regular expression
.
EndsWith: Tests whether a String ends with a particular String
.
Matches: Tests whether a String matches a regular expression
.
StartsWith: Tests whether a String starts with a particular String
the collectionassert class
Similar to the StringAssert class, CollectionAssert is a helper class that is used to make
assertions about a collection of items. Some of the assertions are as follows:
.
AllItemsAreNotNull: Tests that none of the items in a collection is a null reference
.
AllItemsAreUnique: Tests that no duplicate items exist in a collection
.
Contains: Tests whether a collection contains a particular object
.
IsSubsetOf: Tests whether a collection is a subset of another collection
the expectedexception attribute
Sometimes test cases have to execute paths of code that can cause exceptions to be raised. Though
exception coding should be avoided, conditions exist where this might be appropriate. Instead
of writing a test case that includes a Try-Catch block with an appropriate assertion to test that
an exception was raised, you can mark the test case with an ExpectedException attribute. For
example, change the CurrentStatus property to throw an exception if the PaidUp date is prior to
the date the subscription opened, which in this case is a constant:
Vb
Public Const SubscriptionOpenedOn As Date = #1/1/2000#
Public ReadOnly Property CurrentStatus As Status
Get
If Not Me.PaidUpTo.HasValue Then Return Status.Temporary
If Me.PaidUpTo > Now Then
Return Status.Financial
Else
If Me.PaidUpTo >= Now.AddMonths(-3) Then
Return Status.Unfinancial
ElseIf Me.PaidUpTo > SubscriptionOpenedOn Then
Return Status.Suspended
Else
Throw New ArgumentOutOfRangeException( _
“Paid up date is not valid as it is before the subscription opened.”)
End If
End If
End Get
End Property
Code snippet Subscriptions\Subscription.cs
asserting the facts .
205
c#
public static readonly DateTime SubscriptionOpenedOn = new DateTime(2000, 1, 1);
public Status CurrentStatus
{
get
{
if (this.PaidUpTo.HasValue == false)
return Status.Temporary;
if (this.PaidUpTo > DateTime.Today)
return Status.Financial;
else
{
if (this.PaidUpTo >= DateTime.Today.AddMonths(-3))
return Status.Unfinancial;
else if (this.PaidUpTo >= SubscriptionOpenedOn)
return Status.Suspended;
else
throw new ArgumentOutOfRangeException(
“Paid up date is not valid as it is before the subscription opened”);
}
}
}
Code snippet Subscriptions\Subscription.vb
Using the same procedure as before, you can create a separate test case for testing this code path, as
shown in the following example:
Vb
<TestMethod()>
<ExpectedException(GetType(ArgumentOutOfRangeException),
“Argument exception not raised for invalid PaidUp date.”)>
Public Sub CurrentStatusExceptionTest()
Dim target As Subscription = New Subscription
target.PaidUpTo = Subscription.SubscriptionOpenedOn.AddMonths(-1)
Dim expected = Subscription.Status.Temporary
Assert.AreEqual(expected, target.CurrentStatus, _
“This assertion should never actually be evaluated“)
End Sub
Code snippet SubscriptionTests\SubscriptionTest.vb
c#
[TestMethod()]
[ExpectedException(typeof(ArgumentOutOfRangeException),
“Argument Exception not raised for invalid PaidUp date.”)]
public void CurrentStatusExceptionTest()
{
206 .
chaPter 11 uniT TeSTing
Subscription target = new Subscription();
target.PaidUpTo = Subscription.SubscriptionOpenedOn.AddMonths(-1);
var expected = Subscription.Status.Temporary;
Assert.AreEqual(expected, target.CurrentStatus,
“This assertion should never actually be evaluated“);
}
Code snippet SubscriptionTests\SubscriptionTest.cs
The ExpectedException attribute not only catches any exception raised by the test case; it also
ensures that the type of exception matches the type expected. If no exception is raised by the test
case, this attribute will cause the test to fail.
initializinG and cleaninG uP
Despite Visual Studio generating the stub code for test cases you are to write, typically you have
to write a lot of setup code whenever you run a test case. Where an application uses a database,
that database should be returned to its initial state after each test to ensure that the test cases are
completely repeatable. This is also true for applications that modify other resources such as the file
system. Visual Studio provides support for writing methods that can be used to initialize and clean