Как вы макете файловую систему в C# для модульного тестирования?

Существуют ли какие-либо библиотеки или методы для моделирования файловой системы в C# для написания модульных тестов? В моем текущем случае у меня есть методы, которые проверяют, существует ли определенный файл и читают дату создания. Мне может понадобиться больше, чем это в будущем.

13 ответов

Решение

Редактировать: установить пакет NuGet System.IO.Abstractions,

Этот пакет не существовал, когда этот ответ был первоначально принят. Оригинальный ответ предоставляется для исторического контекста ниже:

Вы можете сделать это, создав интерфейс:

interface IFileSystem {
    bool FileExists(string fileName);
    DateTime GetCreationDate(string fileName);
}

и создание "реальной" реализации, которая использует System.IO.File.Exists() и т. д. Затем вы можете смоделировать этот интерфейс, используя среду для имитации; Я рекомендую Moq.

Редактировать: кто-то сделал это и любезно опубликовал это здесь.

Я использовал этот подход для макетирования DateTime.UtcNow в интерфейсе IClock (действительно очень полезно для нашего тестирования, чтобы иметь возможность контролировать течение времени!) И, более традиционно, интерфейс ISqlDataAccess.

Другой подход может заключаться в использовании TypeMock, это позволяет вам перехватывать вызовы классов и выводить их из строя. Это, однако, стоит денег и должно быть установлено на ПК всей вашей команды и на сервере сборки для запуска, также, очевидно, оно не будет работать для System.IO.File, так как не может заглушить mscorlib.

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

Install-Package System.IO.Abstractions

Эта воображаемая библиотека существует сейчас, есть пакет NuGet для https://www.nuget.org/packages/System.IO.Abstractions, который абстрагирует пространство имен System.IO.

Существует также набор помощников по тестированию System.IO.Abstractions.TestingHelpers, который на момент написания статьи реализован лишь частично, но является очень хорошей отправной точкой.

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

Пример:

interface IFileWrapper { bool Exists(String filePath); }

class FileWrapper: IFileWrapper
{
    bool Exists(String filePath) { return File.Exists(filePath); }        
}

class FileWrapperStub: IFileWrapper
{
    bool Exists(String filePath) 
    { return (filePath == @"C:\myfilerocks.txt"); }
}

Используя System.IO.Abstractions и System.IO.Abstractions.TestingHelpers следующим образом:

public class ManageFile {
   private readonly IFileSystem _fileSystem;
   public ManageFile(IFileSystem fileSystem){

      _fileSystem = fileSystem;
   }

   public bool FileExists(string filePath){}
       if(_fileSystem.File.Exists(filePath){
          return true;
       }
       return false;
   }
}

В своем тестовом классе вы используете MockFileSystem() для имитации файла и создаете экземпляр ManageFile, например:

var mockFileSysteme = new MockFileSystem();
var mockFileData = new MockFileData("File content");
mockFileSysteme.AddFile(mockFilePath, mockFileData );
var manageFile = new ManageFile(mockFileSysteme);

Я рекомендую использовать http://systemwrapper.codeplex.com/ поскольку он предоставляет оболочки для наиболее часто используемых типов в пространстве имен System.

Вы можете сделать это с помощью Microsoft Fakes без необходимости менять свою кодовую базу, например, потому что она уже была заморожена.

Сначала создайте поддельную сборку для System.dll - или любого другого пакета, а затем смоделируйте ожидаемые результаты, как показано в:

using Microsoft.QualityTools.Testing.Fakes;
...
using (ShimsContext.Create())
{
     System.IO.Fakes.ShimFile.ExistsString = (p) => true;
     System.IO.Fakes.ShimFile.ReadAllTextString = (p) => "your file content";

      //Your methods to test
}

Я столкнулся со следующими решениями для этого:

  • Пишите интеграционные тесты, а не юнит-тесты. Чтобы это работало, вам нужен простой способ создания папки, куда вы можете создавать дампы, не беспокоясь о помехах других тестов. У меня есть простой класс TestFolder, который может создать уникальную папку для каждого метода тестирования.
  • Написать издевательский System.IO.File. Это создать IFile.cs. Я обнаружил, что использование этого часто заканчивается тестами, которые просто доказывают, что вы можете писать насмешливые операторы, но используйте его, когда использование ввода-вывода невелико.
  • Изучите слой абстракции и извлеките файл ввода-вывода из класса. Создать интерфейс для этого. Остальные используют интеграционные тесты (но это будет очень мало). Это отличается от вышеизложенного тем, что вместо того, чтобы делать файл. Читайте намерение, скажем, ioThingie.loadSettings ()
  • System.IO.Abstractions. Я еще не использовал это, но это тот, с которым мне больше всего нравится играть.

В итоге я использую все методы, описанные выше, в зависимости от того, что я пишу. Но большую часть времени я думаю, что абстракция неправильна, когда я пишу модульные тесты, которые попадают в ввод-вывод.

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

Отредактировано, чтобы добавить: Размышляя об этом немного больше, я не думаю, что вы хотите издеваться над файловой системой, чтобы протестировать этот тип методов. Если вы притворяетесь, что файловая система возвращает true, если определенный файл существует, и используете его в своем тесте метода, который проверяет, существует ли этот файл, то вы ничего не тестируете. В противном случае было бы полезно использовать макетирование файловой системы, если вы хотите протестировать метод, который зависит от файловой системы, но активность файловой системы не является неотъемлемой частью тестируемого метода.

Создание интерфейса и проверка его для тестирования - самый чистый путь. Тем не менее, в качестве альтернативы можно взглянуть на Microsoft Moles Framework.

Было бы сложно смоделировать файловую систему в тесте, поскольку файловые API-интерфейсы.NET на самом деле не основаны на интерфейсах или расширяемых классах, которые можно было бы смоделировать.

Однако, если у вас есть свой собственный функциональный уровень для доступа к файловой системе, вы можете сделать это в модульном тесте.

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

Чтобы ответить на ваш конкретный вопрос: Нет, нет библиотек, которые позволили бы вам имитировать файловые вызовы ввода / вывода (о которых я знаю). Это означает, что для "правильного" модульного тестирования ваших типов потребуется учитывать это ограничение при определении типов.

Краткое примечание о том, как определить "правильный" модульный тест. Я считаю, что модульные тесты должны подтвердить, что вы получите ожидаемый результат (будь то исключение, вызов метода и т. Д.) При условии известных входных данных. Это позволяет вам настроить условия вашего модульного теста как набор входов и / или входных состояний. Лучший способ, который я нашел, - это использовать основанные на интерфейсе сервисы и внедрение зависимостей, чтобы каждая ответственность, внешняя по отношению к типу, обеспечивалась через интерфейс, передаваемый через конструктор или свойство.

Итак, помня об этом, вернемся к вашему вопросу. Я высмеял вызовы файловой системы, создав IFileSystemService интерфейс вместе с FileSystemService реализация, которая является просто фасадом над методами файловой системы mscorlib. Мой код затем использует IFileSystemService а не типы mscorlib. Это позволяет мне подключить мой стандарт FileSystemService когда приложение работает или издевается IFileSystemService в моих модульных тестах. Код приложения один и тот же независимо от того, как он выполняется, но базовая инфраструктура позволяет легко тестировать этот код.

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

Общим решением является использование некоторого абстрактного API файловой системы (например, Apache Commons VFS для Java): вся логика приложения использует API, а модульное тестирование может имитировать реальную файловую систему с реализацией заглушки (эмуляция в памяти или что-то в этом роде).

Для C# существует похожий API: NI.Vfs, который очень похож на Apache VFS V1. Он содержит реализации по умолчанию как для локальной файловой системы, так и для файловой системы в памяти (последняя может использоваться в модульных тестах из коробки).

В настоящее время мы используем проприетарный механизм обработки данных, и его API не представлен в качестве интерфейсов, поэтому мы вряд ли сможем выполнить модульное тестирование нашего кода доступа к данным. Затем я пошел с подходом Мэтта и Джозефа.

Я бы пошел с ответом Джейми Иде. Не пытайтесь издеваться над тем, что вы не написали. Будут всевозможные зависимости, о которых вы не знали - закрытые классы, не виртуальные методы и т. Д.

Другой подход заключается в том, чтобы обернуть методы appopiate чем-то, что может быть смешным. Например, создайте класс с именем FileWrapper, который разрешает доступ к методам File, но это то, что вы можете макетировать.

Другие вопросы по тегам