2009-11-08 7 views
1

Je teste un code qui fonctionne lorsque des assemblys sont chargés dans un domaine d'application. Pour les tests unitaires (dans l'hôte de test intégré de VS2k8) je tourne une nouvelle, unique nommé appdomain avant chaque test avec l'idée qu'il devrait être « propre »:Assemblages mystérieusement chargés dans les nouveaux AppDomains

[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(); 
} 
} 

Si chaque test est exécuté individuellement Je trouve que cela fonctionne correctement; le domaine d'application est créé et ne contient que les quelques assemblages prévus. Toutefois, si plusieurs tests sont exécutés successivement, chaque _testAppDomain a déjà des assemblys chargés à partir de tous les tests précédents. Assez curieusement, les deux tests obtiennent des domaines d'application avec des noms différents. Les assemblys de test qui définissent MissingRegistration et InvalidFactories (deux assemblages différents) ne sont jamais chargés dans le domaine d'application par défaut du test unitaire. Quelqu'un peut-il expliquer ce comportement?

+0

Mise à jour: Cela a quelque chose à voir avec appSetup.ApplicationBase. L'utilisation d'un chemin différent pour chaque exécution permettra d'éviter les effets secondaires indésirables. – Eric

+0

Update2: Le problème ne se reproduit pas si les mêmes méthodes sont exécutées dans le même ordre dans une application de console. Peut-être que l'hôte de test VS cause des problèmes. – Eric

Répondre

0

Il semble que ce qui se passe est que les assemblys sont chargés dans l'AppDomain parent. Si oui, votre bug est dans les détails de la façon dont vous utilisez l'utilisation _testAppDomain ailleurs dans votre code de test, maintenant comment vous le créez.

Idéalement, le code du faisceau de test doit s'exécuter dans AppDomain, puis la méthode exécutée dans AppDomain doit effectuer le chargement réel des assemblages testés.

Voici un exemple pour donner l'idée de la façon de garder la AppDomain mère de jamais ensembles chargement de vos tests:

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 
    } 
} 

Dans votre situation particulière, cependant, il se peut que vous ne l'avez pas vraiment tous les assemblages d'essai en soi, alors peut-être que cela ne s'applique pas. J'ai également remarqué que votre commentaire [TestInitialize] indique qu'il est appelé une fois par test, mais IIRC, les docs disent que le framework de test de Visual Studio ne l'appelle qu'une fois par classe lorsqu'il exécute plusieurs tests. J'utilise un cadre différent, donc je ne suis pas sûr de cela.

Mise à jour

Maintenant que je peux voir le reste de votre code, je peux vous voir prenez déjà des précautions raisonnables pour ne pas charger les assemblées dans la AppDomain mère. Vous dites que cela ne se produit vraiment pas, et je vous crois. Si le problème s'aggrave, vous pouvez essayer que votre SupportingClass appelle un autre assemblage qui fera ensuite votre test, mais je ne pense pas que cela changerait quoi que ce soit.

J'ai une autre théorie pour vous:

J'ai lu quelque part (sur un blog, je pense) que les méthodes JITed sont mises en cache et réutilisés entre AppDomains à partir d'une signature qui inclut certaines des règles de charge de l'assemblage. Je suppose que cela inclurait ApplicationBase. Donc, ma théorie est que quand NET Framework charge votre assembly (ou peut-être quand il charge votre SupportingClass), il recherche les méthodes déjà JITed qu'il peut utiliser. Quand il trouve la méthode précédemment JITed il "l'active" (faute d'un meilleur mot) dans ce AppDomain, ce qui déclenche une charge d'assemblage dont il dépend. Cela expliquerait pourquoi Changin ApplicationBase le fait fonctionner: Les méthodes JITed ne seraient pas réutilisées à partir du cache, et puisque la méthode n'est jamais appelée, elle n'est pas encore JITed pour que l'assembly dépendant ne soit jamais chargé.

Il me semble que vous avez une bonne solution de contournement pour faire varier l'ApplicationBase, ou vous pouvez essayer de faire varier la preuve si l'ApplicationBase est important de garder la même chose. Je pense que cela aurait le même effet.

+0

Bonne idée, mais pas d'amour. J'ai mis à jour mon post original pour refléter les résultats de cette expérience. J'ai également ajouté un formatage correct (je n'ai jamais posté auparavant). – Eric

Questions connexes