Monday 26 May 2014

Visual Studio Upgrade : Private accessor vs Private Object/Type

There are some interesting scenarios that can arise when upgrading a solution built using Visual Studio 2010 to a higher version of Visual Studio (2012 or 2013). If the original solution uses "Private Accessor" feature, a feature that was introduced in an earlier version of Visual Studio to allow users to write test cases that can target private methods, fields or properties of a class, then there is a possibility that you might get compilation error similar to the one described below  after upgrading the solution (along with a warning that this particular feature is deprecated):

System.IO.FileNotFoundException: Could not load file or assembly 'Fully Qualified Assembly Name' or one of its dependencies. The system cannot find the file specified.
File name: 'Fully Qualified Assembly Name'
   at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
   at System.Reflection.RuntimeAssembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
   at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
   at System.Reflection.RuntimeAssembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean forIntrospection)
   at System.Reflection.RuntimeAssembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
   at System.Reflection.Assembly.Load(String assemblyString)
   at System.UnitySerializationHolder.GetRealObject(StreamingContext context)

   at Microsoft.VisualStudio.TestTools.UnitTesting.Publicize.Shadower.ShadowAssemblyHelper(ShadowerOptions options)
   at Microsoft.VisualStudio.TestTools.UnitTesting.Publicize.Shadower.ShadowAssembly(AppDomain domain, ShadowerOptions options)
   at Microsoft.VisualStudio.TestTools.UnitTesting.Publicize.Shadower.ShadowAssembly(ShadowerOptions options)
   at Microsoft.VisualStudio.TestTools.BuildShadowReferences.BuildShadowTask.Execute()
   at Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()
   at Microsoft.Build.BackEnd.TaskBuilder.d__20.MoveNext()


As it happened in my case, chances are the all the necessary references required to load the assembly are  already in place (I verified it using FusLogVw) and yet the compilation process causes grief.
 
So I resorted to the Visual Studio Unit Test Upgrade guidance description and decided to replace the private accessors with PrivateObject and PrivateType. There are some fine examples (#1, #2, #3 etc.) available over the internet that explain about usage of these with respect to instance level access and Type level access.
 
Though in my personal opinion using this approach is just marginally better than using "InternalsVisibleTo" approach - In ideal circumstances, we should  not be worried about internal implementation when dealing with unit test cases. They should always cater to public API only and the effort spent in designing the application should need to pay attention to "testability" of the application as well. If we do that, then we will never need to go back to such hacks (if I may say so). Nevertheless, these are quite interesting wrappers that may be useful in things other than unit testing too for example - cases when you do not want to write elaborate code that uses Reflection etc.

No comments:

Post a Comment