The key characters
You may very well run into the same issue I did if you are using the following technologies:
- Team Foundation Server (TFS) with Team Foundation Build (TFB) - in my case, 2010
- Visual Studio Unit Tests - also 2010, in my case
- Reliance on loading assemblies at runtime: Assembly.Load()
How might someone run into #3, you might ask? It's pretty common these days teeming with reliance on reflections and other fancy run-time logic. In my case, it was
Enterprise Library.
Issue
I was seeing an issue in which the code was succesfully building and passing tests on all development machines, and meanwhile were building but failing tests on the server.
In the unit test logs, the server was claiming that it could load find "Microsoft.Practices.EnterpriseLibrary.Logging.dll".
Investigation
Anytime when dealing with assembly load failures, I like to enable more verbose logging on the issue. There is a handy trick of adding a value in the registry. Add Key: HKLM\Software\Microsoft\Fusion Value: EnableLog (DWORD) with a value of 1. As a result, I found where the unit tests were searching for the missing assembly, and therefore I could identify where they were running:
C:\Builds\8\TheProject\ContinousBuildForWeb\TestResults\TFSBUILD01$_TFSBUILD01 2012-02-17 09_09_33_Any CPU_Debug\Out
In this directory, it was plain to see that the assembly in question(Microsoft.Practices.EnterpriseLibrary.Logging.dll) was missing.
Oddly enough, this assembly was added as a dependency in the project for the unit test's assembly. Additionally,
CopyLocal was true. The missing assembly was correctly showing up in the bin\$(Configuration) directory on the build server. It seems that Team Foundation Build is not able to figure out that the unit tests are dependent on the missing assembly. The key to this oddity is that the assembly is a run-time dependency. I noticed that
ILSpy is also unable to identify dependencies like these. I didn't figure this out on my own, other folks on the 'net have run into failed unit tests in Team Foundation Build to missing assemblies:
http://tempuri.org/tempuri.html.
Solution(s)
There are a couple solutions I'm aware of, but I'm afraid they're both hacks.
Hack A:
One option is to force Team Foundation Build to copy these assemblies explicitly. One way to accomplish this is to make use of unit test settings. In these, you can define additional files and directories to deploy with your unit tests (look in the Deployment section of the configuration).
Hack B: (the one I chose)
Another way to ensure Team Foundation Build will copy these run-time dependencies is to turn them into hard, compile-time dependencies. And that's just what I did by adding a bogus unit test:
[TestMethod]
public void CopyAssemblyHackTest()
{
Microsoft.Practices.EnterpriseLibrary.Logging.ContextItems nullItems = null;
Assert.IsNull(nullItems);
}
Yeah!
Needless Philosophical Reflection
It would be easy to blame Team Foundation Build for this, which I ultimately do, of course. I'm unsure if there would be a better way to handle this. Given just the unit test assemblies, it genuinely may not be possible to determine these run-time dependencies. It could optionally just copy everything out the bin directories, but if someone is doing non-clean builds, this may copy more than necessary.