70-565 - Unit testing - private, protected, internal members and types

Filed under: , , by:

Unit testing is something that most developers are familiar with and a lot of companies also require writing them. The idea of unit testing is to test the smallest testable part of the application, but only part that is public - public properties and public methods. Anything that is non-public cannot be utilized from outside so it can be tested when testing the public member, so the unit tests should be written the way to cover the code that touches private, internal or protected members. That is especially true if you do the Test-Driven Developement. However, sometimes it is valid to test those non-visible members when the number of tests to write in that scenario is significantly smaller than normally a developer would have to write the test those members via public members, which is most common in large or complex applications. This post is not intended to demonstrate the pros and cons of testing non-public members and whether such practice should be avoided or followed, but to demonstrate how to perform those tests if needed or required. Below is a class library project with one class that has all non-public members and a separate test project with a normal unit test.
[Demo.cs]

The class has private and public constructor, private, protected and internal fields and methods and a private class with a private field. The test purpose will be to have ability to access all those members, which can be done in few ways:

  • using Microsft.VisualStudio.TestTools.UnitTesting.PrivateObject class. PrivateObject requires a type and it uses reflections to access all members, so it doesn't require any other operations and works with any Visual Studio edition that has UnitTesting Framework.
    Below is the unit test for the Demo class using PrivateObject class.
    [PrivateObject_Test.cs]
  • using PrivateAccessor. PrivateAccessor is an assemly with a target class with all its members made as public. The assembly name is TargetAssemblyName_Accessor.dll. It can be created straight from IDE (requires Visual Studio 2008 Professional or higher) or from command line using Publicize.exe tool. Once the assembly has been referenced within a test unit project, unit tests can access and validate all members of the target class, which within the accessor assembly has a name TargetClass_Accessor. Below is the unit test for the Demo class using the PrivateAccessor, and if to compare this code with the code above, tesing with PrivateAccessor is more elegant but such tests are executed against an accessor class / accessor assembly, which is not the class / assembly that will be delivered to the user plus any changes in a target class also require regenerating the accessor assembly so a developer must be extra cautious when using the private accessor to make sure that he/she is testing the right verions of a class / assembly.
    [PrivateAccessor_Test.cs]
But what if we don't want to touch private members and only be able to test protected and internal ones. Then it can be done in the following ways:
  • protected members - create a class within a unit test and have that class inherit from the base class and output the results via public property.
  • internal members - add InternalsVisibleToAttribute either to AssemblyInfo.cs file in or your class with internal members. The attribute requies the assembly name with the unit tests to which the internal members will be made visible i.e. [assembly:InternalsVisibleTo("Exam_70_565.UnitTests.ClassLibrary.Test")]
Please note that the intention of this post wasn't to encourage you to test the "hidden" members, but to make you aware of the means to do that and those means should be the last resort as the unit tests should be used to validate the public part of your application, which is the part that will be consumed and is expected to work as stated in the requirements. Although testing smaller non-visible parts of the application can be very beneficial, it can sometimes lead to writinng fewer tests against public members that integrate those non-visible parts, which could lead to introducing a bug in the member that is consumed by client applications and is expected to work without any bugs.

0 comments: