程序集神秘地加载到新的AppDomain中

时间:2009-11-08 05:23:45

标签: assemblies mstest appdomain

我正在测试一些代码,这些代码在程序集加载到appdomain时都能正常工作。对于单元测试(在VS2k8的内置测试主机中),我在每次测试之前启动一个新的,具有唯一名称的appdomain,并认为它应该是“干净的”:

[TestInitialize()]  
public void CalledBeforeEachTestMethod()  
{  
 AppDomainSetup appSetup = new AppDomainSetup();  
 appSetup.ApplicationBase = @"G:\<ProjectDir>\bin\Debug";
 Evidence baseEvidence = AppDomain.CurrentDomain.Evidence;  
 Evidence evidence = new Evidence( baseEvidence );  
 _testAppDomain = AppDomain.CreateDomain( "myAppDomain" + _appDomainCounter++, evidence, appSetup );  
}  

[TestMethod]
public void MissingFactoryCausesAppDomainUnload()
{
 SupportingClass supportClassObj = (SupportingClass)_testAppDomain.CreateInstanceAndUnwrap(
GetType().Assembly.GetName().Name,
typeof( SupportingClass ).FullName );  
 try  
 {  
  supportClassObj.LoadMissingRegistrationAssembly();  
  Assert.Fail( "Should have nuked the app domain" );  
 }  
 catch( AppDomainUnloadedException ) { }  
}

[TestMethod]
public void InvalidFactoryMethodCausesAppDomainUnload()
{
 SupportingClass supportClassObj = (SupportingClass)_testAppDomain.CreateInstanceAndUnwrap(
GetType().Assembly.GetName().Name,
typeof( SupportingClass ).FullName );  
 try  
 {  
  supportClassObj.LoadInvalidFactoriesAssembly();  
  Assert.Fail( "Should have nuked the app domain" );  
 }  
 catch( AppDomainUnloadedException ) { }  
}

public class SupportingClass : MarshalByRefObject
{
 public void LoadMissingRegistrationAssembly()  
 {  
  MissingRegistration.Main();  
 }  
 public void LoadInvalidFactoriesAssembly()  
 {  
  InvalidFactories.Main();  
 }  
}

如果每个测试都单独运行,我发现它可以正常工作; appdomain已创建,只加载了少量预期的程序集。但是,如果连续运行多个测试,则每个_testAppDomain都已从所有先前的测试中加载了程序集。奇怪的是,这两个测试得到了具有不同名称的appdomains。定义MissingRegistration和InvalidFactories(两个不同的程序集)的测试程序集永远不会加载到单元测试的默认appdomain中。任何人都可以解释这种行为吗?

1 个答案:

答案 0 :(得分:0)

听起来正如所发生的那样,程序集正在父AppDomain中加载。如果是这样,您的错误就在于测试代码中其他地方使用_testAppDomain的详细信息,现在就是如何创建它。

理想情况下,测试工具代码应该在AppDomain中运行,然后在AppDomain中运行的方法应该对测试中的组件进行实际加载。

以下是一个示例,说明如何防止父AppDomain加载测试程序集:

void TestRunner()
{
  testProxy =
    (TestProxy)_testAppDomain.CreateInstanceAndUnwrap(
                      typeof(TestProxy).Assembly.FullName,
                      typeof(TestProxy).FullName)

  testProxy.RunTest(testAssembly, typeName);
}

public class TestProxy : MarshalByRefObject
{
  public void Runtest(string testAssembly, string typeName)
  {
    var testType = Assembly.Load(testAssembly).GetType(typeName);

    // run tests in testType using reflection or whatever
  }
}

但是,在您的特定情况下,您可能本身并没有任何测试程序集,因此可能不适用。

我还注意到你的[TestInitialize]评论说每次测试都会调用一次,但是IIRC,文档说Visual Studio的测试框架在运行多个测试时每个类只调用一次。我使用不同的框架,所以我不确定。

<强>更新

现在我可以看到你的其余代码了,我可以看到你已经采取了合理的预防措施,不在父AppDomain中加载程序集。你说那确实没有发生,我相信你。如果情况变得更糟,你可以尝试让你的SupportingClass调用另一个程序集然后进行测试,但我真的不认为这会改变任何东西。

我确实有另一种理论:

我在某个地方(我认为在博客上)读到JITed方法在AppDomains之间基于包含一些程序集加载规则的签名进行缓存和重用。我假设这将包括ApplicationBase。

所以我的理论是,当.NET Framework加载你的程序集时(或者当它加载你的SupportingClass时),它正在扫描它可以使用的已经JITed的方法。当它找到以前的JITed方法时,它会在该AppDomain中“启用”它(因为没有更好的单词),这会触发它所依赖的程序集加载。

这可以解释为什么changin ApplicationBase使它工作:JITed方法不会从缓存中重用,并且由于该方法永远不会被调用,因此它不再被JIT,因此从不加载依赖程序集。

在我看来,您在改变ApplicationBase时有一个很好的解决方法,或者如果ApplicationBase保持不变,您可以尝试更改证据。我认为这会产生同样的效果。