代码之家  ›  专栏  ›  技术社区  ›  Sam Holder Brian Adams

单元测试动态加载代码

  •  2
  • Sam Holder Brian Adams  · 技术社区  · 16 年前

    我在阅读,发现这段代码是在向一个问题提问

    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 回复  |  直到 16 年前
        1
  •  1
  •   Esko Luontola    16 年前

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

    1. 从插件目录查找.dll文件。

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

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

    当算法被分离成这三个部分时,可以单独地测试部件1和3(尽管在技术上,第1部分的测试不是单元测试),因为它触及文件系统,除非C语言有某种方式来模拟文件系统,如Java 7的NIO2文件系统API应该是可模仿的。您还可以通过模拟第2部分来对将它们放在一起的代码进行单元测试。

        2
  •  4
  •   Darin Dimitrov    16 年前

    通过这样重构它:

    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;
    }
    
        3
  •  1
  •   Carl Manaster    16 年前

    我将把内环体提取成一种方法。我会努力让那个方法返回T实例,或者如果测试失败则返回空值。现在我可以编写单元测试了。

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

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

        4
  •  0
  •   Lasse V. Karlsen    16 年前

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

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

        5
  •  0
  •   Thorarin    16 年前

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

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

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

    但是,您可能会滥用数据驱动的测试机制来实现这一点。您可以在加载所有X实现(在类_initialize或其他任何方法中)之后,在一些临时数据库表中插入一个数字0…X-1。 为每个测试设置该表,测试将运行X次,并以数字作为输入数据。用它作为你的 implementations 列出(存储在成员变量中)并在该实现上运行测试。

    是的,非常难看……您将失去执行实际数据驱动测试的能力,而不会增加更多的丑陋。