C# 单元测试动态加载代码

C# 单元测试动态加载代码,c#,unit-testing,reflection,C#,Unit Testing,Reflection,我在阅读时发现这段代码是对一个问题的回答 public List<T> LoadPlugin<T>(string directory) { Type interfaceType = typeof(T); List<T> implementations = new List<T>(); //TODO: perform checks to ensure type is valid foreach (var file

我在阅读时发现这段代码是对一个问题的回答

public List<T> LoadPlugin<T>(string directory)
{
    Type interfaceType = typeof(T);
    List<T> implementations = new List<T>();

    //TODO: perform checks to ensure type is valid

    foreach (var file in System.IO.Directory.GetFiles(directory))
    {
        //TODO: add proper file handling here and limit files to check
        //try/catch added in place of ensure files are not .dll
        try
        {
            foreach (var type in System.Reflection.Assembly.LoadFile(file).GetTypes())
            {
                if (interfaceType.IsAssignableFrom(type) && interfaceType != type)
                { 
                    //found class that implements interface
                    //TODO: perform additional checks to ensure any
                    //requirements not specified in interface
                    //ex: ensure type is a class, check for default constructor, etc
                    T instance = (T)Activator.CreateInstance(type);
                    implementations.Add(instance);
                }
            }
        }
        catch { }
    }

    return implementations;
}
公共列表加载插件(字符串目录)
{
类型接口类型=类型(T);
列表实现=新列表();
//TODO:执行检查以确保类型有效
foreach(System.IO.Directory.GetFiles(Directory)中的var文件)
{
//TODO:在此处添加正确的文件处理并限制要检查的文件
//添加try/catch以代替确保文件不是.dll
尝试
{
foreach(System.Reflection.Assembly.LoadFile(file.GetTypes())中的变量类型)
{
if(interfaceType.IsAssignableFrom(type)&&interfaceType!=type)
{ 
//找到了实现接口的类
//TODO:执行其他检查以确保
//接口中未规定的要求
//例如:确保类型是类,检查默认构造函数等
T instance=(T)Activator.CreateInstance(类型);
添加(实例);
}
}
}
捕获{}
}
返回实现;
}

这让我想知道单元测试这段代码的最佳方式是什么?

我将抽象出动态程序集的检测和加载,这样我就可以模拟该部分并加载测试程序集作为单元测试的一部分


或者,由于您可以指定目录,只需在计算机的临时目录中构造一个临时目录作为单元测试代码的一部分,将单元测试项目中的单个程序集复制到该目录中,并要求插件系统扫描该目录。

通过以下方式重构:

public List<T> LoadPlugin<T>(Type[] types)
{
    Type interfaceType = typeof(T);
    List<T> implementations = new List<T>();

    //TODO: perform checks to ensure type is valid
    try
    {
        foreach (var type in types)
        {
            if (interfaceType.IsAssignableFrom(type) && interfaceType != type)
            { 
                //found class that implements interface
                //TODO: perform additional checks to ensure any
                //requirements not specified in interface
                //ex: ensure type is a class, check for default constructor, etc
                T instance = (T)Activator.CreateInstance(type);
                implementations.Add(instance);
            }
        }
    }
    catch { }

    return implementations;
}
公共列表加载插件(类型[]类型)
{
类型接口类型=类型(T);
列表实现=新列表();
//TODO:执行检查以确保类型有效
尝试
{
foreach(类型中的变量类型)
{
if(interfaceType.IsAssignableFrom(type)&&interfaceType!=type)
{ 
//找到了实现接口的类
//TODO:执行其他检查以确保
//接口中未规定的要求
//例如:确保类型是类,检查默认构造函数等
T instance=(T)Activator.CreateInstance(类型);
添加(实例);
}
}
}
捕获{}
返回实现;
}

您没有说您使用的是哪个单元测试框架,所以我在这里假设Visual Studio的内置功能

您需要将有问题的程序集添加到部署项列表中,以便将它们复制到单元测试工作目录

我看到的问题是在所有不同的实现上运行单元测试。在一个测试中测试所有的实现将不清楚哪些实现失败。您希望为每个实现运行一次测试

然而,您可能会严重滥用数据驱动测试机制来实现这一点。加载所有x实现(在类中)后,可以在某个临时数据库表中插入数字0…x-1。 为每个测试设置该表,测试将运行x次,并将数字作为输入数据。将其用作
实现
列表(存储在成员变量中)的索引,并对该实现运行测试


是的,非常难看。。您将失去在不增加更多丑陋性的情况下进行实际数据驱动测试的能力。

我将把内部循环体提取到一个方法中。我将努力让该方法返回T实例,如果测试失败,则返回null。现在我可以编写单元测试了

if (interfaceType.IsAssignableFrom(type) && interfaceType != type)
    return (T)Activator.CreateInstance(type);
else
    return null;

现在,我可以为这些新函数类型(我希望返回非null实例)和类型(我希望返回null实例)提供数据。代码的其余部分似乎都在使用系统调用,我倾向于相信这些调用。但是如果你没有,那就单独测试一下。测试
GetFiles()
是否返回正确的文件列表;测试
GetTypes()
是否在给定文件中为您提供了正确的类型,等等。

在该方法中有三个不相关的操作(请参阅)。它们中的每一个都应该被分离到它们自己的类中,该类实现了一些接口,以便您可以模拟它们以获得更好的可测试性。这些树是:

  • 从插件目录中查找.dll文件

  • 加载.dll并获取其包含的类型。这应该是一个调用API方法的单行程序。您实际上不需要对此进行测试(至少不需要在单元测试中进行测试),因为您可以合理地假设编程语言的库工作正常

  • 创建插件类型的实例


  • 当算法分为这三部分时,您可以单独对第1部分和第3部分进行单元测试(尽管从技术上讲,第1部分的测试不是单元测试,因为它涉及到文件系统,除非C#有办法模拟文件系统,比如Java 7的NIO2文件系统API应该是可模拟的)。通过模仿第2部分,您还可以对将它们放在一起的代码进行单元测试。

    建议在单元测试中使用文件系统不是异端邪说吗;)视情况而定。就我个人而言,我不认为单元测试与其他宗教一样,会乐于使用别人认为不受限制的零碎东西。不过,我必须说,如果可能的话,模仿文件系统将是我的第一选择。