testing Private Members
One of the selling points of unit testing is that it is particularly effective for testing the internals
of your class to ensure that they function correctly. The assumption here is that if each of your
components works in isolation, there is a better chance that they will work together correctly; and
in fact, you can use unit testing to test classes working together. However, you might be wondering
how well the unit-testing framework handles testing private methods.
One of the features of the .NET Framework is the capability to reflect over any type that has been
loaded into memory and to execute any member regardless of its accessibility. This functionality
does come at a performance cost, because the reflection calls obviously include an additional level
of redirection, which can prove costly if done frequently. Nonetheless, for testing, reflection enables
you to call into the inner workings of a class and not worry about the potential performance
penalties for making those calls.
The other, more significant issue with using reflection to access non-public members of a class
is that the code to do so is somewhat messy. Fortunately, Visual Studio 2010 does a very good
job of generating a wrapper class that makes testing even private methods easy. To show this,
return to the CurrentStatus property, change its access from public to private, and rename it
PrivateCurrentStatus. Then regenerate the unit test for this property as you did earlier.
The following code snippet is the new unit-test method that is generated:
Vb
<TestMethod(), _
DeploymentItem(“Subscriptions.dll“)> _
Public Sub PrivateCurrentStatusTest()
Dim target As Subscription_Accessor = New Subscription_Accessor()
‘ TODO: Initialize to an appropriate value
Dim actual As Subscription.Status
actual = target.PrivateCurrentStatus
Assert.Inconclusive(“Verify the correctness of this test method.”)
End Sub
Code snippet SubscriptionTests\SubscriptionTest.vb
c#
[TestMethod()]
[DeploymentItem(“Subscriptions.dll“)]
public void PrivateCurrentStatusTest()
{
Subscription_Accessor target = new Subscription_Accessor();
Subscription.Status actual;
actual = target.PrivateCurrentStatus;
Assert.Inconclusive(“Verify the correctness of this test method.”);
}
Code snippet SubscriptionTests\SubscriptionTest.cs
214 .
chaPter 11 uniT TeSTing
As you can see, the preceding example uses an instance of a new Subscription_Accessor class to
access the PrivateCurrentStatus property. This is a class that was auto-generated and compiled
into a new assembly by Visual Studio. A new file was also added to the test project with the
.accessor extension, which is what causes Visual Studio to create the new accessor classes.
You can add a private accessor class to a test project without generating a unit test. To do this, open
the class that you want a private accessor for and select Create Private Accessor from the context
menu of the editor.
You don’t need to create a private accessor for every class in a project
individually. Each .accessor file actually relates of a single project and creates an
accessor class for all of the classes in that project.
testinG code contracts
If you are using the new Code Contracts feature described in Chapter 13, then you might want to
write tests that verify the behavior of your contracts. The simplest way to do this is to open the
Code Contracts project properties page and uncheck the Assert on Contract Failure checkbox.
When you do this the Code Contracts API will raise exceptions instead of causing Assertion
failures. You can check for these exceptions with an ExpectedException attribute if you know
the type of exception to expect. By default, the Code Contracts tools generate the exceptions that
will be thrown and their type cannot be known at runtime. Many of the contract methods have an
overload which accepts an exception type as a generic parameter.
Here is a simple class which performs a mathematical operation on positive integers and a unit test
to check the case where a negative number is passed in.
Vb
Class Calculator
Public Function Factorial(ByVal n As Integer) As Integer
Contract.Requires(Of ArgumentOutOfRangeException)(n > = 0, “ n “ )
If n = 0 Then Return 1
Return n * Factorial(n -1)
End Function
End Class
< TestMethod(), ExpectedException(GetType(ArgumentOutOfRangeException)) >
Public Sub NegativeTest()
Dim generator As New Calculator()
Dim actual = generator.Factorial( - 1)
Assert.Fail( “ Contract not working “ )
End Sub
Code snippet CodeContracts1\CalculatorTests.vb
Testing Code Contracts .
215
c#
class Calculator
{
public int Factorial(int n)
{
Contract.Requires<ArgumentOutOfRangeException>(n >= 0, “n“);
if (n == 0) return 1;
return n * Factorial(n - 1);
}
}
[TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void NegativeTest()
{
var generator = new Calculator();
var actual = generator.Factorial(-1);
Assert.Fail(“Contract not working“);
}
Code snippet CodeContracts1\CalculatorTests.cs
Although this method of testing Code Contracts works, it is not really recommended as it may
cover up errors in the code. A better option is to hook into the Code Contracts system and
override its default behavior from within the test project itself. You do this by registering for
the ContractFailed event on the static Contract class inside of an AssemblyInitialize
method. Inside of the event handler you tell the Code Contracts API that you have handled the
contract failure and that you would like to throw an appropriate exception.
Vb
<AssemblyInitialize()>
Public Shared Sub AssemblyInitialize(ByVal testContext As TestContext)
AddHandler Contract.ContractFailed, Sub(sender As Object,
e As ContractFailedEventArgs)
e.SetHandled()
e.SetUnwind()
End Sub
End Sub
<TestMethod(), ExpectedException(GetType(Exception), AllowDerivedTypes:=True)>
Public Sub NegativeTest()
Dim generator As New Calculator()
216 .
chaPter 11 uniT TeSTing
Dim actual = generator.Factorial( - 1)
Assert.Fail( “ Contract not working “ )
End Sub
Code snippet CodeContracts2\CalculatorTests.vb
c#
[AssemblyInitialize]
public static void AssemblyInitialize(TestContext testContext)
{
Contract.ContractFailed += (s, e) = >
{
e.SetHandled();
e.SetUnwind();
};
}
[TestMethod , ExpectedException(typeof(Exception), AllowDerivedTypes = true)]
public void NegativeTest()
{
var generator = new Calculator();
var actual = generator.Factorial( - 1);
Assert.Fail( “ Contract not working “ );
}
Code snippet CodeContracts2\CalculatorTests.cs
When Code Contracts are configured to cause Asserts, the intended exception
is lost, so the code sample checks for any subclass of Exception. The
actual exception that gets thrown is a System.Diagnostics.Contracts
.ContractException, which is private to the .NET Framework, so you can’t
detect it directly.
ManaGinG larGe nuMbers of tests
Visual Studio provides both the Test View window and the Test List Editor to display a list of all of
the tests in a solution. The Test View window, which was shown earlier in the chapter in Figure 11 - 2,
simply displays the unit tests in a fl at list. However, if you have hundreds, or even thousands, of unit
tests in your solution, trying to manage them with a fl at list will quickly become unwieldy.
The Test List Editor enables you to group and organize related tests into test lists. Because test lists
can contain both tests and other test lists, you can further organize your tests by creating a logical,
hierarchical structure. All the tests in a test list can then be executed together from within Visual
Studio, or via a command-line test utility.
summary .
217
You can open the Test List Editor from the
Test Windows menu, or you can double-click
the Visual Studio Test Metadata (.vsmdi) file
for the solution. Figure 11-10 shows the Test
List Editor for a solution with a number of
tests organized into a hierarchical structure
of related tests.
On the left in the Test List Editor window is
a hierarchical tree of test lists available for
the current solution. At the bottom of the tree
are two project lists, one showing all the test
cases (All Loaded Tests) and one showing those test cases that haven’t been put in a list (Tests Not
in a List). Under the Lists of Tests node are all the test lists created for the project.
To create a new test list, click Test . Create New Test List. Test cases can be dragged from any
existing list into the new list. Initially, this can be a little confusing because a test will be moved to
the new list and removed from its original list. To add a test case to multiple lists, either hold the
Ctrl key while dragging the test case or copy and paste the test case from the original list to the new
list.
After creating a test list, you can run the whole list by checking the box next to the list in the Test
Manager. The Run button executes all lists that are checked. Alternatively, you can run the list with
the debugger attached using the Debug Checked Tests menu item.
suMMary
This chapter described how you can use unit testing to ensure the correct functionality of your
code. The unit-testing framework within Visual Studio is quite comprehensive, enabling you to both
document and manage test cases.
You can fully exercise the testing framework using an appropriate data source to minimize the
repetitive code you have to write. You can also extend the framework to test all the inner workings
of your application.
Visual Studio Premium and Ultimate contain even more functionality for testing, including the
ability to track and report on code coverage, and support for load and web application testing.
Chapter 56 provides more detail on these advanced testing capabilities.
fiGure 11-10
12
Documentation with XMl
Comments
what’s in this chaPter?
.
Adding inline documentation to your code using XML comments
.
Using the GhostDoc Visual Studio Add-In to automatically generate
XML comments
.
Producing stand-alone documentation from XML comments with
Sandcastle
.
Using Task List comments to keep track of pending coding tasks
and other things to do
Documentation is a critical, and often overlooked, feature of the development process.
Without documentation, other programmers, code reviewers, and project managers have
a more difficult time analyzing the purpose and implementation of code. You can even
have problems with your own code once it becomes complex, and having good internal
documentation can aid in the development process.
XML comments are a way of providing that internal documentation for your code without
having to go through the process of manually creating and maintaining a separate document.
Instead, as you write your code, you include metadata at the top of every definition to explain
the intent of your code. Once the information has been included in your code, it can be
consumed by Visual Studio to provide Object Browser and IntelliSense information.
GhostDoc is a free third-party add-in for Visual Studio that can automatically insert an XML
comment block for a class or member.
Sandcastle is a set of tools that act as a documentation compiler. These tools can be used
to easily create standalone documentation in Microsoft compiled HTML help or Microsoft
Help 2 format from the XML comments you have added to your code.
220 .
chaPter 12 documenTATion WiTh xml commenTS
inline coMMentinG
All programming languages supported by Visual Studio provide a method for adding inline
documentation. By default, all inline comments are highlighted in green.
C# supports both single line comments and comment blocks. Single line comments are denoted by
// at the beginning of the comment. Block comments typically span multiple lines and are opened
by /* and closed off by */, as shown in the following code:
c#
// Calculate the factorial of an integer
public int Factorial(int number)
{
/* This function calculates a factorial using an
* iterative approach.
*/
int intermediateResult = 1;
for (int factor = 2; factor <= number; factor++)
{
intermediateResult = intermediateResult * factor;
}
return intermediateResult; //The calculated factorial
}
VB just uses a single quote character to denote anything following it to be a comment, as shown in
the following code:
Vb
' Calculate the factorial of an integer
Public Function Factorial(ByVal number As Integer) As Integer
' This function calculates a factorial using an
' iterative approach.
'
Dim intermediateResult As Integer = 1
For factor As Integer = 2 To number
intermediateResult = intermediateResult * factor
Next
Return intermediateResult 'The calculated factorial
End Function
xMl coMMents
XML comments are specialized comments that you include in your code. When the project goes
through the build process, Visual Studio can optionally include a step to generate an XML file based
on these comments to provide information about user-defined types such as classes and individual
members of a class (user defined or not), including events, functions, and properties.
XMl Comments .
221
XML comments can contain any combination of XML and HTML tags. Visual Studio performs
special processing on a particular set of predefined tags, as you see throughout the bulk of this
chapter. Any other tags are included in the generated documentation file as is.
adding xMl comments
XML comments are added immediately before the property, method, or class definition they are
associated with. Visual Studio automatically adds an XML comment block when you type the