Подтвердить что ты не робот

Модульный ввод/вывод файлов

Просматривая существующие теги, связанные с тестированием модулей, здесь, в Stack Overflow, я не смог найти однозначный ответ о том, как выполнять операции ввода/вывода файлов unit test. Я только недавно начал изучать модульное тестирование, предварительно узнав о преимуществах, но с трудом привык к написанию тестов в первую очередь. Я создал свой проект, чтобы использовать NUnit и Rhino Mocks, и хотя я понимаю концепцию, лежащую в их основе, у меня есть небольшая проблема с пониманием того, как использовать Mock Objects.

В частности, у меня есть два вопроса, на которые я бы ответил. Во-первых, каков правильный способ работы с файлами ввода/вывода unit test? Во-вторых, в моих попытках узнать об модульном тестировании я столкнулся с инъекцией зависимостей. После создания и работы Ninject мне было интересно, следует ли использовать DI внутри моих модульных тестов или просто создавать объекты непосредственно.

4b9b3361

Ответ 1

Отъезд Учебник по TDD с помощью Rhino Mocks и SystemWrapper.

SystemWrapper обертывает многие из классов System.IO, включая File, FileInfo, Directory, DirectoryInfo,.... Вы можете увидеть полный список.

В этом уроке я покажу, как выполнять тестирование с помощью MbUnit, но он точно такой же для NUnit.

Ваш тест будет выглядеть примерно так:

[Test]
public void When_try_to_create_directory_that_already_exists_return_false()
{
    var directoryInfoStub = MockRepository.GenerateStub<IDirectoryInfoWrap>();
    directoryInfoStub.Stub(x => x.Exists).Return(true);
    Assert.AreEqual(false, new DirectoryInfoSample().TryToCreateDirectory(directoryInfoStub));

    directoryInfoStub.AssertWasNotCalled(x => x.Create());
}

Ответ 2

При тестировании файловой системы не обязательно делать что-то одно. По правде говоря, есть несколько вещей, которые вы можете сделать, в зависимости от обстоятельств.

Вопрос, который вам нужно задать, - это: что я тестирую?

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

  • Чтобы файлы были сохранены в нужном месте? Хорошо, как вы знаете, что такое подходящее место? Предположительно, у вас есть код, который сочетает в себе путь с именем файла. Это код, который вы можете легко протестировать: ваш ввод состоит из двух строк, и ваш вывод должен быть строкой, которая является допустимым расположением файла, построенным с использованием этих двух строк.

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

  • Но мне нужно что-то сделать с файлами, которые я получаю. Для этого теста вы должны использовать фальшивку для своего класса-получателя. Ваша подделка должна вернуть жесткий список файлов. Если вы используете реальный файлообменник и настоящий файловый процессор, вы не будете знать, какой из них вызывает сбой теста. Таким образом, ваш класс файлового процессора при тестировании должен использовать поддельный класс файл-геттер. Ваш класс файлового процессора должен использовать интерфейс file-getter. В реальном коде вы перейдете в настоящий файл-получатель. В тестовом коде вы передадите поддельный файл-получатель, который возвращает известный статический список.

Основные принципы:

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

Ответ 3

Q1:

У вас есть три варианта.

Вариант 1: жить с ним.

(нет примера: P)

Вариант 2: при необходимости создайте небольшую абстракцию.

Вместо того, чтобы выполнять файл I/O (File.ReadAllBytes или что-то еще) в тестируемом методе, вы можете изменить его так, чтобы IO выполнялось снаружи и вместо него передавался поток.

public class MyClassThatOpensFiles
{
    public bool IsDataValid(string filename)
    {
        var filebytes = File.ReadAllBytes(filename);
        DoSomethingWithFile(fileBytes);
    }
}

станет

// File IO is done outside prior to this call, so in the level 
// above the caller would open a file and pass in the stream
public class MyClassThatNoLongerOpensFiles
{
    public bool IsDataValid(Stream stream) // or byte[]
    {
        DoSomethingWithStreamInstead(stream); // can be a memorystream in tests
    }
}

Этот подход является компромиссом. Во-первых, да, это более проверяемо. Тем не менее, он торгует тестируемостью для небольшого дополнения к сложности. Это может повлиять на ремонтопригодность и объем кода, который вы должны написать, плюс вы можете просто переместить свою тестовую проблему на один уровень.

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

Вариант 3: Оберните всю файловую систему

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

Я пошел по этому пути раньше; У меня была завернутая реализация файловой системы, но в конце я просто удалил ее. В API были тонкие различия, мне приходилось вводить их повсюду, и в конечном итоге это было лишней болью для небольшого выигрыша, поскольку многие из классов, использующих его, были для меня не очень важны. Если бы я использовал контейнер IoC или писал что-то критичное, и тесты должны были быть быстрыми, я мог бы застрять с ним. Как и во всех этих параметрах, ваш пробег может отличаться.

Что касается вашего вопроса о контейнере IoC:

Ввести тестовые удваивания вручную. Если вам нужно выполнить много повторяющихся действий, просто используйте методы установки / factory в своих тестах. Использование контейнера IoC для тестирования будет чрезмерным излишеством! Возможно, я не понимаю ваш второй вопрос.

Ответ 4

В настоящее время я использую объект IFileSystem через инъекцию зависимостей. Для производственного кода класс-оболочка реализует интерфейс, обертывая определенные функции ввода-вывода, которые мне нужны. При тестировании я могу создать нулевую или заглушку и предоставить ее тестируемому классу. Проверенный класс не является более мудрым.