код динамической загрузки модульного теста

2

Я читал и нашел этот код как вопрос к вопросу

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

и мне стало интересно, что лучший способ unit test этого кода?

Теги:
unit-testing
reflection

5 ответов

1
Лучший ответ

Есть три несвязанных вещи, которые выполняются в этом одном методе (см. SRP). Каждый из них должен быть разделен на собственный класс, который реализует некоторый интерфейс, чтобы вы могли высмеивать их для лучшей проверки. Дерево:

  • Поиск DLL файлов из каталога плагинов.

  • Загрузка DLL и получение типов, которые он содержит. Это должен быть однострочный, который вызывает методы API. Вам не нужно тестировать это (по крайней мере, не в модульных тестах), потому что вы можете разумно предположить, что библиотеки языка программирования работают правильно.

  • Создание экземпляров типов плагинов.

Когда алгоритм разделен на эти три части, вы можете unit test части 1 и 3 изолировать (хотя технически тесты для части 1 не являются модульными тестами, поскольку они касаются файловой системы, если у С# не есть способ издеваться над файловой системой, например API-интерфейсом API Java NIO2, должен быть макет). Вы также можете unit test код, который ставит их все вместе, издеваясь над частью 2.

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

Я бы извлек тело внутреннего цикла в метод. Я бы работал над тем, чтобы этот метод возвращал экземпляр T, или null, если он не прошел тест. Теперь я могу написать модульные тесты.

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

Теперь я могу прокормить эти новые типы функций, для которых я ожидаю вернуть ненулевой экземпляр, и типы, для которых я ожидаю возврат null. Весь остальной код, похоже, использует системные вызовы, и я склонен доверять этим. Но если вы этого не сделаете - тогда испытайте их, в изоляции. Проверьте, что GetFiles() возвращает правильный список файлов; проверьте, что GetTypes() дает вам нужные типы в данном файле и т.д.

0

Я бы осветил обнаружение и загрузку динамических сборок таким образом, чтобы я мог издеваться над этой частью и загрузить тестовую сборку в рамках моих модульных тестов.

Или, поскольку вы можете указать каталог, просто создайте в качестве части вашего кода unit test временный каталог в каталоге TEMP вашего компьютера скопируйте на него одну сборку из вашего проекта unit test и спросите плагиновую систему для сканирования этого каталога.

  • 1
    Не ересь ли предложить использовать файловую систему в модульном тесте;)
  • 1
    По-разному. Лично я не считаю модульное тестирование таким же религиозным, как другие, и с удовольствием использую кусочки, которые другие считают запрещенными. Хотя я должен сказать, что, если это вообще возможно, я выбрал бы насмешку над файловой системой.
0

Вы не сказали, какой модуль тестирования модулей вы используете, поэтому я буду использовать встроенные возможности Visual Studio.

Вам нужно будет добавить собранные сборки в список элементов развертывания для их копирования в рабочий каталог unit test.

Проблема, которую я вижу, - это запуск модульных тестов для всех различных реализаций. Тестирование всех реализаций в одном тесте не даст понять, какая из реализаций не выполняется. Вы хотите запускать тесты один раз для каждой реализации.

Вы могли бы ужасно злоупотреблять механизмом проверки данных, чтобы сделать это, однако. Вы можете вставлять числа 0... x-1 в некоторую временную таблицу базы данных после загрузки всех реализаций x (в вашем Class_Initialize или что-то еще). Настройте эту таблицу для каждого из ваших тестов, и тест будет выполняться x раз, с номером в качестве входных данных. Используйте это как индекс в своем implementations List (хранящийся в переменной-члене) и запустите тест для этой реализации.

Да, очень уродливый.. и вы теряете способность выполнять фактический тест с данными, не добавляя больше уродства.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню