With the work I’ve been doing on versioning I’ve had to write unit tests that verify the behavior I expect from the helper classes in Microsoft.Activities.dll. If you want to verify that your assembly versioning strategy is working correctly you may have to do similar testing. This sort of testing is tricky… in this post I’ll share with you my solutions to some tough problems.
Test Problem: Multiple Versions of the Same Assembly in a Test Run
There is only one deployment directory for the test run and all deployment items are copied there. I can’t deploy ActivityLibrary V1 and V2 to the same test directory but I need to deploy both for a test run (note: I did not tackle GAC deployment for this set of tests)
The solution comes in two parts.
- How to create different versions of the assemblies in one build
- How to deploy the different versions of the assemblies when testing
Creating Different Versions of the Assemblies in One Build
For my testing I need a variety of different assemblies in debug and release builds with different versions, signing options and references to other assemblies
- ActivityLibrary – Version 1 (signed and unsigned), Version 2 (signed and unsigned)
- Workflow – Version 1 (signed), Version 2 (signed, unsigned)
To do this I created a number of projects that produce the same assembly and write the output to the bin directory for all build configurations (rather than bin\debug or bin\release)
As you can see I have several ActivityLibrary projects with different names but they all produce ActivityLibrary.dll and the same is true for WorkflowLibrary
Deploy the Different Versions of the Assemblies When Testing
I deploy different versions of the assemblies into subdirectories of the test directory. Then when I create the AppDomain for the test I set the ApplicationBase to the subdirectory for that test. This ensures that the test directory contains the versions of the assemblies that I want.
To make the test code less error prone, I created constants to define the versions of assemblies and combinations of directories where I will deploy them and I pass these values to the [DeploymentItem] attribute
Test Problem: Assembly.Load and Cached Assemblies in the AppDomain
The biggest issue I ran into when writing my tests is caused by the behavior of Assembly.Load. The issue is that when you call Assembly.Load it checks to see if it has already loaded an assembly that will satisfy the request and will use that assembly. When you have a number of tests that need to verify if the correct assembly was loaded you find that suddenly you have a test order dependency. What happens is that when you run a test by itself it passes but when you run all of your tests some of them fail.
I want to be sure that when I run my tests that I always get the same results no matter how many I run or in what order. To solve this problem I need to deal with the assemblies cached in the AppDomain and there is no way to unload an assembly once it has been loaded.
To solve this problem for each test I’m going to create a new AppDomain run the test code in the new AppDomain and then Unload it when I’m finished. This ensures that I start with an empty AppDomain and I can verify that only the assemblies I want are loaded.
My test class StaticXamlHelperTest has a matching worker class StaticXamlTestWorker which does the actual testing in the new AppDomain.
Then I added two helper methods to the StaticXamlHelperTest class to create the AppDomain and create the Worker class in the AppDomain
Then for each test I follow a simple pattern
- Create the AppDomain (line 7)
- Create the worker inside a try block and call the test method (line 10)
- In the finally block Unload the AppDomain (line 16)
Test Problem: How to Know Which Version of the Activity Library Was Actually Loaded
Problems with assembly loading generally result in exceptions being thrown but sometimes you might be surprised to find the workflow loading and happily running with a version of the activity that is something other than what you expected.
Since I am testing infrastructure I created Workflows with the sole purpose of loading an activity from an Activity Library and returning the version of the assembly that contained the activity. My activity is named GetAssemblyVersion.
I then create a Workflow that declares an OutArgument<Version> and uses the GetAssemblyVersion activity.
Because I’m testing Microsoft.Activities.StaticXamlHelper I create a partial class with an overloaded constructor that calls the method I really want to test StaticXamlHelper.InitializeComponent
Now I’m ready for some test code – remember this code will run in the new AppDomain and it makes use of Microsoft.Activities.UnitTesting
Here is how it works
- Line 3 - Create the activity using the overloaded constructor providing the list of assemblies you want to reference (provided by a helper method)
- Line 4 - Create a test host – Microsoft.Activities.UnitTesting.WorkflowInvokerTest
- Line 5 - Test the activity
- Line 6 – Assert the out argument “AssemblyVersion” is 18.104.22.168
If this sounds complicated… that’s just because it is. The complete source for all the unit tests is included in the Microsoft.Activities source so you can check out the details of how it works.
I know… you are thinking this sounds like way too much work for your project. If you only knew how many bugs I discovered in my code and fixed before you ever saw them (including one very obscure bug that didn’t appear on VS2010 RTM but only on VS2010 SP1 beta) you would take the time to write some quality code.