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

149

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

pupeno
источник
1
Это похоже на дубликат нескольких других, включая: stackoverflow.com/questions/664277/… .
Джон Сондерс
Может быть, попробуйте заглянуть в pex ( research.microsoft.com/en-us/projects/pex/filesystem.pdf )
Tinus
2
@Mitch: в большинстве случаев достаточно поместить данные в файловую систему и позволить модульным тестам пройти свой курс. Однако я столкнулся с методами, которые выполняют много операций ввода-вывода, и настройка тестовой среды для таких методов значительно упрощается благодаря использованию фиктивной файловой системы.
Стив Гвиди
Для этой цели (и более) я написал github.com/guillaume86/VirtualPath , он все еще WIP, и API, безусловно, изменится, но он уже работает, и некоторые тесты включены.
Guillaume86

Ответы:

154

Редактировать: установить пакет 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 .

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

Мэтт Хауэллс
источник
1
На мой взгляд, создание интерфейса, как описывает Мэтт, - это путь. Я даже написал инструмент, который генерирует такие интерфейсы для вас, который полезен при попытке макетировать статические и / или запечатанные классы или недетерминированные методы (например, часы и генераторы случайных чисел). См. Jolt.codeplex.com для получения дополнительной информации.
Стив Гвиди
Похоже, что репозиторий в указанной статье был удален / перемещен без предварительного уведомления. Тем не менее, здесь, похоже, есть полный пакет его усилий: nuget.org/packages/mscorlib-mock
Mike-E
У Typemock есть ограничения на то, какие типы являются поддельными, но (по крайней мере, в текущей версии по состоянию на октябрь 2017 года) вы определенно можете подделать статический класс File. Я только что проверил это сам.
Райан Родемойер
Можете ли вы подвести итоги некоторых интеграционных тестов?
Озкан
83

Install-Package System.IO.Abstractions

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

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

Двоичный Беспорядок
источник
3
Я думаю, что стандартизация вокруг этой уже построенной абстракции - лучший выбор. Никогда не слышал об этой библиотеке, так что большое спасибо за заголовки.
Jullealgon
PM означает менеджер пакетов .. чтобы открыть ... Инструменты> Диспетчер пакетов NuGet> Консоль диспетчера пакетов
thedanotto
11

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

Пример:

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"); }
}
Джозеф
источник
5

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

adeel41
источник
В настоящее время я использую эту библиотеку, и теперь, когда я обнаружил, что ее абстракции для таких вещей, как FileStream, не включают IDisposable, я ищу замену. Если библиотека не позволяет мне правильно распоряжаться потоками, я не могу рекомендовать (или использовать) ее для обработки таких операций.
Джеймс Наиль
1
IFileStreamWrap SystemWrapper теперь реализует IDisposable.
tster
systemwrapper - это только .net framework, он вызовет странные проблемы при использовании с .netcore
Adil H. Raza
3

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

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

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

Майкл Ллойд Ли млк
источник
4
Ссылка на IFile.cs не работает.
Майк-Э
3

Используя 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);
Оливье Мартиал Соро
источник
2

Вы можете сделать это с помощью 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
}
Бахадыр Исмаил Айдын
источник
1

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

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

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

LBushkin
источник
1

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

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

Джейми Иде
источник
1

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

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

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

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

akmad
источник
1

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

Konamiman
источник
0

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

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

Виталий Федорченко
источник
-1

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

Тиен До
источник
-2

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

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

gbanfill
источник