单元测试动态加载代码

时间:2009-07-23 20:22:47

标签: 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 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;
}

它让我想知道对这段代码进行单元测试的最佳方法是什么?

5 个答案:

答案 0 :(得分:4)

通过这种方式重构:

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

答案 1 :(得分:1)

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

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

现在我可以提供这些新函数类型,我期望返回非空实例,以及我希望返回null的类型。所有其余的代码似乎都在使用系统调用,我倾向于相信这些。但如果你不这样做 - 那就单独测试那些。测试GetFiles()为您提供正确的文件列表;测试GetTypes()为您提供给定文件中的正确类型等。

答案 2 :(得分:1)

在这一种方法中完成了三个不相关的事情(参见SRP)。它们中的每一个都应该分离到它们自己的类,它实现了一些接口,这样你就可以模拟它们以获得更好的可测试性。树的东西是:

  1. 从插件目录中找出.dll文件。

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

  3. 创建插件类型的实例。

  4. 当算法被分成这三个部分时,你可以单独测试第1部分和第3部分(虽然技术上第1部分的测试不是单元测试,因为它接触文件系统,除非C#有某种方式来模拟文件系统,如Java 7的NIO2文件系统API应该是可模拟的)。您还可以通过模拟第2部分来单元测试将它们放在一起的代码。

答案 3 :(得分:0)

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

或者,既然您可以指定一个目录,只需构建一个临时目录作为单元测试代码的一部分,在计算机的TEMP目录中,将单个程序集从单元测试项目复制到它,然后询问插件系统扫描该目录。

答案 4 :(得分:0)

您没有说明您正在使用哪个单元测试框架,因此我将假设Visual Studio的内置功能。<​​/ p>

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

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

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

是的,非常难看..你失去了进行实际数据驱动测试的能力,而不会增加更多的丑陋。