up around test cases. (Again, attributes are used to mark the appropriate methods that should be
used to initialize and clean up the test cases.)
The attributes for initializing and cleaning up around test cases are broken down into three levels:
those that apply to individual tests, those that apply to an entire test class, and those that apply to
an entire test project.
testinitialize and testcleanup
As their names suggest, the TestInitialize and TestCleanup attributes indicate methods that
should be run before and after each test case within a particular test class. These methods are
useful for allocating and subsequently freeing any resources that are needed by all test cases in the
test class.
classinitialize and classcleanup
Sometimes, instead of setting up and cleaning up after each test, it can be easier to ensure
that the environment is in the correct state at the beginning and end of running an entire test
class. Previously, we explained that test classes are a useful mechanism for grouping test cases;
this is where you put that knowledge to use. Test cases can be grouped into test classes that
contain one method marked with the ClassInitialize attribute and another marked with the
ClassCleanup attribute. These methods must both be marked as static, and the one marked with
ClassInitialize must take exactly one parameter that is of type UnitTesting.TestContext,
which is explained later in this chapter.
assemblyinitialize and assemblycleanup
The fi nal level of initialization and cleanup attributes is at the assembly, or project, level. Methods
that are intended to initialize the environment before running an entire test project, and cleaning up
after, can be marked with the AssemblyInitialize and AssemblyCleanup attributes, respectively.
Because these methods apply to any test case within the test project, only a single method can
be marked with each of these attributes. Like the class - level equivalents, these methods must
both be static and the one marked with AssemblyInitialize must take a parameter of type
UnitTesting.TestContext .
For both the assembly - level and class - level attributes, it is important to remember that even if only
one test case is run, the methods marked with these attributes will also be run.
When you use the Create Unit Test menu to generate a unit test, it generates
stubs for the TestInitialize , TestCleanup , ClassInitialize , and
ClassCleanup methods in a source code region that is commented out.
It is a good idea to put the methods marked with AssemblyInitialize and
AssemblyCleanup together into their own test class to make them easy to fi nd.
If there is more than one method marked with either of these attributes, then
running any tests in the project results in a runtime error.
testinG context
When you are writing test cases, the testing engine can assist you in a number of ways, including
by managing sets of data so you can run a test case with a range of data, and by enabling you
to output additional information for the test case to aid in debugging. This functionality is
available through the TestContext object that is generated within a test class and passed into the
AssemblyInitialize and ClassInitialize methods.
data
The CurrentStatusTest method generated in the fi rst section of this chapter tested only a single
path through the CurrentStatus property. To fully test this method, you could have written
additional statements and assertions to set up and test the Subscription object. However, this
process is fairly repetitive and would need to be updated if you ever changed the structure of the
CurrentStatus property. An alternative is to provide a DataSource for the CurrentStatusTest
Testing Context . 207
208 .
chaPter 11 uniT TeSTing
method whereby each row of data tests a different path through the property. To add appropriate
data to this method, use the following process:
1 Create a local SQL CE database and database table to store the various test data. In this
case, create a database called LoadTest with a table called Subscription_CurrentStatus.
The table has an Identity bigint column called Id, a nullable datetime column called
PaidUp, and an nvarchar(20) column called Status.
2 Add appropriate data values to the table to cover all paths
through the code. Test values for the CurrentStatus
property are shown in Figure 11-7.
3 Select the appropriate test case in the Test View window
and open the Properties window. Select the Data
Connection String property and click the ellipsis button
to open the Connection Properties dialog.
fiGure 11-7
4 Use the Connection Properties dialog to connect to the database created in Step 1. You
should see a connection string similar to the following:
data source=|DataDirectory|\LoadTest.sdf
5 If the connection string is valid, a drop-down box appears when you select the DataTable
property, enabling you to select the database table you created in Step 1.
6 To open the test case in the main window, return to the Test View window and select Open
Test from the right-click context menu for the test case. Notice that a DataSource attribute
has been added to the test case. This attribute is used by the testing engine to load the
appropriate data from the specified table. This data is then exposed to the test case through
the TestContext object.
If you are using a SQL Server CE database, you’ll also get a DeploymentItem
attribute added by default. This ensures that the database will be copied if the
test assembly is deployed to another location.
7 Modify the test case to access data from the TestContext object and use the data to drive
the test case, which gives you the following CurrentStatusTest method:
Vb
<DataSource(“System.Data.SqlServerCe.3.5”, _
“data source=|DataDirectory|\LoadTest.sdf”, _
“Subscription_CurrentStatus”, DataAccessMethod.Sequential)> _
<DeploymentItem(“SubscriptionTests\LoadTest.sdf”)> _
<TestMethod()>_
Public Sub CurrentStatusDataTest()
Dim target As Subscription = New Subscription
Testing Context .
209
If Not IsDBNull(Me.TestContext.DataRow.Item(“PaidUp”)) Then
target.PaidUpTo = CType(Me.TestContext.DataRow.Item(“PaidUp”), Date)
End If
Dim val As Subscription.Status = _
CType([Enum].Parse(GetType(Subscription.Status), _
CStr(Me.TestContext.DataRow.Item(“Status”))), Subscription.Status)
Assert.AreEqual(val, target.CurrentStatus, _
“Subscripiton.CurrentStatus was not set correctly.”)
End Sub
Code snippet SubscriptionTests\SubscriptionTest.vb
c#
[DataSource(“System.Data.SqlServerCe.3.5”,
“data source=|DataDirectory|\\LoadTests.sdf”,
“Subscription_CurrentStatus”,
DataAccessMethod.Sequential)]
[DeploymentItem(“SubscriptionTests\\LoadTests.sdf“)]
[TestMethod()]
public void CurrentStatusDataTest()
{
var target = new Subscription();
var date = this.TestContext.DataRow[“PaidUp“] as DateTime?;
if (date != null)
{
target.PaidUpTo = date;
}
var val = Enum.Parse(typeof(Subscription.Status),
this.TestContext.DataRow[“Status“] as string);
Assert.AreEqual(val, target.CurrentStatus,
“Subscription.CurrentStatus was not set correctly.”);
}
Code snippet SubscriptionTests\SubscriptionTest.cs
When this test case is executed, the CurrentStatusTest method is executed four times (once for
each row of data in the database table). Each time it is executed, a DataRow object is retrieved
and exposed to the test method via the TestContext.DataRow property. If the logic within the
CurrentStatus property changes, you can add a new row to the Subscription_CurrentStatus
table to test any code paths that may have been created.
Before moving on, take one last look at the DataSource attribute that was applied to the
CurrentStatusTest. This attribute takes four arguments, the first three of which are
used to determine which DataTable needs to be extracted. The remaining argument is a
DataAccessMethod enumeration, which determines the order in which rows are returned from the
DataTable. By default, this is Sequential, but it can be changed to Random so the order is different
every time the test is run. This is particularly important when the data is representative of end user
data but does not have to be processed in any particular order.
210 . chaPter 11 uniT TeSTing
writing test output
Writing unit tests is all about automating the process of testing an application. Because of this,
these test cases can be executed as part of a build process, perhaps even on a remote computer. This
means that the normal output windows, such as the console, are not a suitable place for outputting
test - related information. Clearly, you also don ’ t want test - related information interspersed
throughout the debugging or trace information being generated by the application. For this reason,
there is a separate channel for writing test - related information so it can be viewed alongside the test
results.
The TestContext object exposes a WriteLine method that takes a String and a series of String.
Format arguments that can be used to output information to the results for a particular test. For
example, adding the following line to the CurrentStatusDataTest method generates additional
information with the test results:
Vb
TestContext.WriteLine( “ No exceptions thrown for test id {0} ” , _
CInt(Me.TestContext.DataRow.Item(0)))
Code snippet SubscriptionTests\SubscriptionTest.vb
c#
TestContext.WriteLine( “ No exceptions thrown for test id {0} ” ,
this.TestContext.DataRow[0]);
Code snippet SubscriptionTests\SubscriptionTest.cs
Data - driven tests are not just limited to database tables; they can be driven by
Excel spreadsheets or even from Comma - Separated Values (CSV) fi les.
Although you should use the TestContext.WriteLine method to capture details
about your test executions, the Visual Studio test tools will collect anything
written to the standard error and standard output streams and add that data to
the Test Results window.
After the test run is completed, the Test Results window is displayed, listing all the test cases that
were executed in the test run along with their results. The Test Results Details window, shown in
advanced Unit Testing .
211
Figure 11-8, displays any additional information that was
outputted by the test case. You can view this window by
double-clicking the test case in the Test Results window.
In Figure 11-8, you can see in the Additional Information
section the output from the WriteLine method you added to
the test method. Although you added only one line to the test
method, the WriteLine method was executed for each row
in the database table. The Data Driven Test Results section
of Figure 11-8 provides more information about each of the
test passes, with a row for each row in the table. Your results
may differ from those shown in Figure 11-8, depending on
the code you have in your Subscription class.
adVanced unit testinG
Up until now, you have seen how to write and execute unit tests. This section goes on to examine
how you can add custom properties to a test case, and how you can use the same framework to test
private methods and properties.
custom Properties
The testing framework provides a number of test attributes that you can apply to a method
to record additional information about a test case. This information can be edited via the
Properties window and updates the appropriate attributes on the test method. At times you
might want to drive your test methods by specifying your own properties, which can also be
set using the Properties window. To do this, add TestProperty attributes to the test method.
For example, the following code adds two attributes to the test method to enable you to specify
an arbitrary date and an expected status. This might be convenient for ad hoc testing using the
Test View and Properties window:
Vb
<TestMethod()>
<TestProperty(“SpecialDate”, “1/1/2008”)>
<TestProperty(“SpecialStatus”, “Suspended”)>
Public Sub SpecialCurrentStatusTest()
Dim target As New Subscription
target.PaidUpTo = CType(Me.TestContext.Properties.Item(“SpecialDate”), _
Date)
Dim val As Subscription.Status = _
[Enum].Parse(GetType(Subscription.Status), _
CStr(Me.TestContext.Properties.Item(“SpecialStatus”)))
Assert.AreEqual(val, target.CurrentStatus, _
fiGure 11-8
212 .
chaPter 11 uniT TeSTing
“Correct status not set for Paidup date {0}”, target.PaidUpTo)
End Sub
Code snippet SubscriptionTests\SubscriptionTest.vb
c#
[TestMethod]
[TestProperty( “ SpecialDate ” , “ 1/1/2008 “ )]
[TestProperty( “ SpecialStatus ” , “ Suspended “ )]
public void SpecialCurrentStatusTest()
{
var target = new Subscription();
target.PaidUpTo = this.TestContext.Properties[ “ SpecialDate “ ] as DateTime?;
var val = Enum.Parse(typeof(Subscription.Status),
this.TestContext.Properties[ “ SpecialStatus “ ] as string);
Assert.AreEqual(val, target.CurrentStatus,
“ Correct status not set for Paidup date {0} ” , target.PaidUpTo);
}
Code snippet SubscriptionTests\SubscriptionTest.cs
By using the Test View to navigate to this test case and accessing
the Properties window, you can see that this code generates two
additional properties, SpecialDate and SpecialStatus, as
shown in Figure 11-9.
You can use the Properties window to adjust the SpecialDate
and SpecialStatus values. Unfortunately, the limitation here is
that there is no way to specify the data type for the values. As a
result, the property grid displays and enables edits as if they were
String data types.
fiGure 11-9
In the previous version of Visual Studio the TestContext.Properties
dictionary was not automatically filled in and you had to do this by hand in your
TestInitialize method. In Visual Studio 2010 this is all handled for you.
advanced Unit Testing .
213