Search code examples
c#unit-testing.net-assembly

How to check if an assembly contains unit tests without loading it?


I'm currently using the following method to check for test assemblies:

private bool IsTestAssembly(string path)
{
    var assembly = Assembly.LoadFrom(path);
    foreach (var type in assembly.GetTypes())
    {
        var a = type.GetCustomAttributes(true).Select(x => x.ToString());
        if (a.Contains("Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute"))
            return true;
    }
    return false;
}

But I would like to check this without loading the assembly in memory because I need to be able to delete it afterwards in case the verification fails.

I was hoping I could simply unload the assembly, but I soon discovered that, according to MSDN:

There is no way to unload an individual assembly without unloading all of the application domains that contain it.

Thanks in advance!


Solution

  • I worked out a short solution as suggested by TheGreatCO, i.e. to load the assembly in a new AppDomain:

    1) Usage:

    // assemblies are unloaded on disposal
    using (var analyser = new AssemblyAnalyser())
    {
        var path = "my.unit.tests.dll";
        var b = analyser.IsTestAssembly(path);
        Assert.IsTrue(b);
    }
    

    2) Implementation:

    public class AssemblyAnalyser : MarshalByRefObject, IDisposable
    {
        public AssemblyAnalyser()
        {
            var evidence = new Evidence(AppDomain.CurrentDomain.Evidence);
            var appSetup = new AppDomainSetup()
            {
                ApplicationBase = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
            };
            appDomain = AppDomain.CreateDomain(otherDomainFriendlyName, evidence, appSetup); 
        }
    
        public bool IsTestAssembly(string assemblyPath)
        {
            if (AppDomain.CurrentDomain.FriendlyName != otherDomainFriendlyName)
            {
                var analyser = appDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, GetType().FullName);
                return ((AssemblyAnalyser)analyser).IsTestAssembly(assemblyPath);
            }
            else
            {
                var assembly = Assembly.LoadFrom(assemblyPath);
                return ContainsTestClasses(assembly);
            }
        }
    
        public void Dispose()
        {
            if (AppDomain.CurrentDomain.FriendlyName != otherDomainFriendlyName)
            {
                AppDomain.Unload(appDomain);
                GC.SuppressFinalize(this);
            }
        }
    
        ~AssemblyAnalyser()
        {
            Dispose();
        }
    
        private bool ContainsTestClasses(Assembly assembly)
        {
            foreach (var type in assembly.GetTypes())
            {
                var attr = type.GetCustomAttributes(true).Select(x => x.ToString());
                if (attr.Contains("Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute"))
                    return true;
            }
            return false;
        }
    
        private const string otherDomainFriendlyName = "AssemblyAnalyser";
    
        private AppDomain appDomain;
    }