Accessing Nonpublic Members from Tests


What if you want to test a class member that is not public? For example, suppose you're writing a private function that is never publicly accessed and is only used internally by other members. You'd like to test this method, but your test code does not have access to private members (or to internal members if the code is in a separate assembly).

There are three main approaches for addressing this issue:

  • Make the private members you need to test public.

  • Ensure that the private members are reachable through a public member and test via those public members.

  • Use .NET reflection in the tests to load and directly invoke the nonpublic members.

With Team System, this final approach is abstracted for you. The following two sections describe how Team System helps to automate this previously manual task.

Important

Testing private members is a controversial subject. Some people prefer to test only via public members to allow for easier refactoring. Others argue that an API should never be modified just for the sake of easier testing. If you agree with the former opinion, you can safely skip the remainder of this section.

Using PrivateObject to access nonpublic instance members

Suppose you'd like to test the private field and method of the following class:

     public class Example     {        public Example() {}        private string password = "letmein";        private bool VerifyPassword(string password)        {            return (String.Compare(this.password, password, false) == 0);        }     } 

Because the field and method are marked private, a unit test will not have the ability to directly access them. How can you ensure that VerifyPassword is working correctly?

Team System introduces the PrivateObject class, which is a wrapper around reflection code that enables you to access nonpublic members in a fairly straightforward manner.

The following table summarizes the methods supported by PrivateObject.

Open table as spreadsheet

Method

Description

GetArrayElement

Returns the selected item from a private array member. Supports multidimensional arrays with additional arguments.

GetField

Returns the value of the target field

GetFieldOrProperty

Returns the value of the target field or property

GetProperty

Returns the value of the target property

Invoke

Invokes the target method, optionally passing parameters

SetArrayElement

Assigns the given value to the indicated element of a private array Supports multidimensional arrays with additional arguments

SetField

Assigns the supplied object to the target field

SetFieldOrProperty

Assigns the supplied object to the target field or property

SetProperty

Assigns the supplied object to the target property

To use it, you first create an instance of the PrivateObject class, passing a Type object for the class you wish to work with:

     using System;     using Microsoft.VisualStudio.TestTools.UnitTesting;     namespace Explorations     {         [TestClass]         public class ExampleTest         {            private PrivateObject privateObject;            const string PASSWORD = &"letmein";            [TestInitialize]            public void TestInitialize()            {               privateObject = new PrivateObject(typeof(Example));            }         }     } 

Now you can create your tests. Use the GetField method of the PrivateObject instance to access non- public fields, supplying the name of the desired variable. Similarly, use Invoke to call methods, supplying the method name as the first argument, followed by any parameters to that method:

     using System;     using Microsoft.VisualStudio.TestTools.UnitTesting;     namespace Explorations     {         [TestClass]         public class ExampleTest         {                private PrivateObject privateObject;                const string PASSWORD = "letmein";                [TestInitialize]                public void TestInitialize()            {                    privateObject = new PrivateObject(typeof(Example));            }            [TestMethod]            public void ComparePrivatePassword()            {                string password = (string)privateObject.GetField("password");                Assert.AreEqual(PASSWORD, password);            }            [TestMethod]            public void TestPrivateVerifyPassword()            {                 bool accepted = (bool)privateObject.Invoke("VerifyPassword", PASSWORD);                 Assert.IsTrue(accepted);            }        }    } 

Because PrivateObject uses reflection, you need to cast the results of these calls from the generic Object type back to the correct underlying type.

Using PrivateType to access nonpublic static members

PrivateObject is used to access instance-based members of a class. If you need to access static non- public members, you use the PrivateType class, which has a very similar interface and is a wrapper of reflection code.

The following table summarizes the methods exposed by PrivateType.

Open table as spreadsheet

Method

Description

GetStaticArrayElement

Returns the selected item from a private static array member. Supports multidimensional arrays with additional arguments.

GetStaticField

Returns the value of the target static field.

GetStaticProperty

Returns the value of the target static property.

InvokeStatic

Invokes the target static method, optionally passing parameters.

SetStaticArrayElement

Assigns the given value to the indicated element of a private static array. Supports multidimensional arrays with additional arguments.

SetStaticField

Assigns the supplied object to the target static field.

SetStaticProperty

Assigns the supplied object to the target static property.

The usage is very similar to the PrivateObject. Create an instance of the PrivateType, indicating which type you wish to work with, and then use the methods to access members as with PrivateObject. Suppose you added a private static count of password failures with a wrapping private property called FailureCount. The following code could read and test that property:

          [TestMethod]          public void TestPrivateStaticFailureCount()          {              PrivateType example = new PrivateType(typeof(Example));              int failureCount = (int)example.GetStaticProperty("FailureCount");              Assert.AreEqual(failureCount, 0);          } 

Again, you create an instance of PrivateType, passing the type reference for the class you wish to access. Then you use that instance, invoking GetStaticProperty to retrieve the value you wish to test. Finally, you ensure that the value is zero as expected.

Note

Use caution when testing static data. Because static data is shared and is not automatically reset between your tests, sometimes the order of your tests will affect their results. In other words, if you test that a value is initialized to zero in one test and then set it to a test value in another test, if the order of those tests is reversed, the zero test will fail. Remember that you must be able to run your tests in any order and in any combination.



Professional Visual Studio 2005 Team System
Professional Visual Studio 2005 Team System (Programmer to Programmer)
ISBN: 0764584367
EAN: 2147483647
Year: N/A
Pages: 220

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net