简体   繁体   English

单元测试动态加载代码

[英]unit test dynamic loading code

I was reading and found this code as an aswer to a question 我正在阅读,发现此代码是对问题的质疑

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

and it got me wondering what the best way to unit test this code would be? 我想知道对代码进行单元测试的最佳方法是什么?

By refactoring it this way: 通过这种方式重构:

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

I would extract the inner loop body into a method. 我将内部循环体提取为一个方法。 I'd work towards having that method return the T instance, or null if it failed the test. 我将努力使该方法返回T实例,如果它未通过测试,则返回null。 Now I can write unit tests. 现在,我可以编写单元测试了。

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

Now I can feed this new function types for which I expect a non-null instance to be returned, and types for which I expect a null return. 现在,我可以提供这些新的函数类型(我希望为其返回非null实例)和一些类型(希望它们返回null)。 All of the rest of the code seems to be using system calls, and I tend to trust those. 其余所有代码似乎都在使用系统调用,我倾向于相信这些。 But if you don't - then test those, in isolation. 但是,如果您不这样做,请单独进行测试。 Test that GetFiles() gives you back the correct list of files; 测试GetFiles()为您提供正确的文件列表; test that GetTypes() gives you the right types in a given file, etc. 测试GetTypes()在给定文件中为您提供正确的类型,等等。

There are three unrelated things that are done in that one method (see SRP ). 在一种方法中完成了三件无关的事情(请参阅SRP )。 Each of them should be separated to their own class, which implements some interface, so that you can mock them for better testability. 它们中的每一个都应该分离到各自的类中,该类实现一些接口,以便您可以模拟它们以提高可测试性。 The tree things are: 树上的东西是:

  1. Finding out the .dll files from the plugin directory. 从插件目录中找到.dll文件。

  2. Loading the .dll and getting the types that it contains. 加载.dll并获取其包含的类型。 This should be a one-liner that calls the API methods. 这应该是调用API方法的单一代码。 You don't really need to test this (at least not in unit tests), because you can reasonably assume that the programming language's libraries work right. 您实际上并不需要进行测试(至少不需要在单元测试中进行测试),因为您可以合理地假设编程语言的库可以正常工作。

  3. Creating instances of the plugin types. 创建插件类型的实例。

When the algorithm is separated into these three parts, you can unit test parts 1 and 3 in isolation (although technically the tests for part 1 are not unit tests, because it touches the file system, unless C# has some way to mock the file system, like Java 7's NIO2 file system API should be mockable). 将算法分为这三个部分时,您可以单独对第1部分和第3部分进行单元测试(尽管从技术上讲,第1部分的测试不是单元测试,因为它涉及文件系统,除非C#有某种方法可以模拟文件系统,例如Java 7的NIO2文件系统API应该是可模拟的)。 You can also unit test the code that puts them all together, by mocking part 2. 您还可以通过模拟第2部分来对将它们组合在一起的代码进行单元测试。

I would abstract out the detection of and loading of the dynamic assemblies in such a way that I could mock that part and load a test assembly as part of my unit tests. 我将以某种方式抽象出动态程序集的检测和加载,以使我可以模拟该零件并将其加载为单元测试的一部分。

Or, since you can specify a directory, simply construct, as part of your unit test code, a temporary directory in the TEMP directory of your computer, copy a single assembly from your unit test project to it, and ask the plugin system to scan that directory. 或者,由于您可以指定目录,因此只需在计算机的TEMP目录中构造一个临时目录(作为单元测试代码的一部分),然后将单个程序集从单元测试项目复制到该程序集,然后要求插件系统进行扫描该目录。

You didn't say which unit testing framework you're using, so I'll assume Visual Studio's built-in capabilities here. 您没有说要使用哪个单元测试框架,因此这里假定Visual Studio的内置功能。

You would need to add the assemblies in question to the list of deployment items for them to get copied to the unit test working directory. 您需要将有问题的程序集添加到部署项列表中,以便将它们复制到单元测试工作目录中。

The problem I see is running the unit tests on all the different implementations. 我看到的问题是在所有不同的实现上运行单元测试。 Testing all implementations in a single test would make it unclear which of the implementations are failing. 在单个测试中测试所有实现将使不清楚哪些实现失败。 You want to run the tests once for each implementation. 您希望为每个实现运行一次测试。

You could horribly abuse the data-driven test mechanism to do this however. 但是,您可能会滥用数据驱动的测试机制来执行此操作。 You could insert a numbers 0...x-1 in some temporary database table after loading all the x implementations (in your Class_Initialize or whatever). 您可以在加载所有x实现(在Class_Initialize或其他方式)后,在一些临时数据库表中插入数字0 ... x-1。 Set up that table for each of your tests, and the test will be run x times, with a number as input data. 为每个测试设置该表,测试将运行x次,并以数字作为输入数据。 Use that as an index to your implementations List (stored in a member variable) and run the test on that implementation. 使用它作为implementations列表的索引(存储在成员变量中),然后对该实现进行测试。

Yes, very ugly.. and you lose the ability to do an actual data-driven test without adding more ugliness. 是的,非常丑陋..并且您失去了进行实际数据驱动测试的能力,而又没有增加更多丑陋感。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM