2009-07-23 3 views
2

я читал и нашел этот код в качестве aswer на вопросблок кода тест динамической загрузки

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

и это заставило меня интересно, что лучший способ модульного тестирования этот код будет?

ответ

1

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

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

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

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

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

0

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

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

+1

Разве это не ересь предложить использовать файловую систему в единичном тесте;) –

+1

Это зависит. Лично я не рассматриваю модульное тестирование как религиозное, как другие, и с удовольствием использую кусочки, которые другие считают вне пределов. Хотя, надо сказать, насмехаться над файловой системой было бы моим первым выбором, если это вообще возможно. –

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

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

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

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

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

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

1

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

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

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

Смежные вопросы