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

Как выполнить интеграционное тестирование в .NET с реальными файлами?

У меня есть некоторые классы, которые реализуют некоторую логику, связанную с файловой системой и файлами. Например, я выполняю следующие задачи в рамках этой логики:

  • проверка, имеет ли определенная папка определенную структуру (например, она содержит подпапки с конкретными именами и т.д.)
  • загрузка некоторых файлов из этих папок и проверка их структуры (например, это некоторые файлы конфигурации, расположенные в определенном месте в определенной папке)
  • загрузить дополнительные файлы для тестирования/проверки из файла конфигурации (например, этот файл конфигурации содержит информацию о других файлах в той же папке, которые должны иметь другую внутреннюю структуру и т.д.)

Теперь вся эта логика имеет некоторый рабочий процесс, и исключения генерируются, если что-то не так (например, файл конфигурации не найден в определенном месте папки). Кроме того, в эту логику вовлечена платформа управляемой расширяемости (MEF), поскольку некоторые из этих файлов, которые я проверяю, являются управляемыми библиотеками DLL, которые я вручную загружаю в агрегаты MEF и т. Д...

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

  • папка с правильной структурой и всеми действующими файлами
  • папка с правильной структурой, но с неверным файлом конфигурации
  • папка с правильной структурой, но без файла конфигурации и т.д...

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

В целом, является ли все это правильным подходом для подобных сценариев тестирования? Есть ли другие лучшие подходы?

4b9b3361

Ответ 1

В первую очередь, я думаю, лучше писать блок-тесты для проверки вашей логики, не касаясь внешних ресурсов. Здесь у вас есть два варианта:

  • вам нужно использовать слой абстракции, чтобы изолировать вашу логику от внешних зависимостей, таких как файловая система. Вы можете легко заглушить или издеваться (вручную или с помощью ограничительной среды изоляции, такой как NSubstitute, FakeItEasy или Moq), это абстракции в модульных тестах. Я предпочитаю этот вариант, потому что в этом случае тесты подталкивают вас к лучшему дизайну.
  • если вам нужно иметь дело с устаревшим кодом (только в этом случае), вы можете использовать одну из неограниченных инфраструктур изоляции (таких как TypeMock Isolator, JustMock или Microsoft Fake), которые могут заглушить/издеваться над всем (например, закрытые и статические классы, не виртуальные методы). Но они стоят денег. Единственным "бесплатным" вариантом является Microsoft Fakes, если вы не являетесь счастливым обладателем Visual Studio 2012/2013 Premium/Ultimate.

В модульных тестах вам не нужно проверять логику внешних библиотек, таких как MEF.

Во-вторых, если вы хотите написать тесты интеграции, вам нужно написать тест "счастливый путь" (когда все в порядке) и некоторые тесты, которые проверяют вашу логику в граничных случаях (файл или каталог не найдены). В отличие от @Sergey Berezovskiy, я рекомендую создавать отдельные папки для каждого тестового примера. Основные преимущества:

  • вы можете предоставить своим папкам значимые имена, которые более четко выражают ваши намерения;
  • вам не нужно писать сложную (то есть хрупкую) логику установки/разрыва.
  • даже если позже вы решите использовать другую структуру папок, вы можете легко изменить его, потому что у вас уже будет рабочий код и тесты (рефакторинг под тестовым журом намного проще).

Для тестов на единицу и интеграции вы можете использовать обычные структурные модули тестирования (например, NUnit или xUnit.NET). С помощью этих фреймворков довольно легко запустить тесты в сценариях непрерывной интеграции на сервере сборки.

Если вы решите написать оба типа тестов, тогда вам нужно отделить модульные тесты от тестов интеграции (вы можете создавать отдельные проекты для всех видов тестов). Причины этого:

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

Ответ 2

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

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

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

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

  • Четкие стандарты: Сделайте правила и цели каждого теста абсолютно ясными.
  • Автоматизация: Возможность повторного запуска тестов быстро и без особых ручных настроек.
  • Повторяемость: Тестовая ситуация, которую вы можете "сбросить", чтобы можно было быстро перезапускать тесты с небольшими изменениями.

Создайте повторяемый тестовый сценарий

В вашей ситуации я бы настроил две основные папки: одну, в которой все как положено (т.е. работает правильно), и одну, в которой все правила нарушены.

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

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

Тестирование

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

Не ясно, насколько вы знакомы с unit-/интеграционным тестированием. В любом случае, я бы порекомендовал NUnit. Мне также нравится использовать расширения в Should. Вы можете получить оба из Nuget:

install-package Nunit
install-package Should

Пакет should-package позволит вам написать тестовый код следующим образом:

someCalculatedIntValue.ShouldEqual(3); 
someFoundBoolValue.ShouldBeTrue();

Обратите внимание, что есть несколько доступных для запуска тестов. Лично у меня был только какой-то реальный опыт работы с бегуном, встроенным в Resharper, но я вполне доволен им, и у меня нет проблем рекомендовать его.

Ниже приведен пример простого тестового класса с двумя тестами. Обратите внимание, что в первом случае мы проверяем ожидаемое значение, используя метод расширения из If, в то время как во втором мы ничего явно не тестируем. Это связано с тем, что он помечен [ExpectedException], что означает, что произойдет сбой, если исключение указанного типа не будет сгенерировано во время выполнения теста. Вы можете использовать это, чтобы убедиться, что выдается соответствующее исключение, когда нарушается одно из ваших правил.

[TestFixture] 
public class When_calculating_sums
{                    
    private MyCalculator _calc;
    private int _result;

    [SetUp] // Runs before each test
    public void SetUp() 
    {
        // Create an instance of the class to test:
        _calc = new MyCalculator();

        // Logic to test the result of:
        _result = _calc.Add(1, 1);
    }

    [Test] // First test
    public void Should_return_correct_sum() 
    {
        _result.ShouldEqual(2);
    }

    [Test] // Second test
    [ExpectedException(typeof (DivideByZeroException))]
    public void Should_throw_exception_for_invalid_values() 
    {
        // Divide by 0 should throw a DivideByZeroException:
        var otherResult = _calc.Divide(5, 0);
    }       

    [TearDown] // Runs after each test (seldom needed in practice)
    public void TearDown() 
    {
        _calc.Dispose(); 
    }
}

Имея все это в своем распоряжении, вы сможете создавать и воссоздавать тестовые сценарии, а также запускать тесты на них простым и воспроизводимым способом.


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

[ExpectedException(typeof(DivideByZeroException), 
   ExpectedMessage="Attempted to divide by zero" )]
public void When_attempting_something_silly(){  
    ...
}

Ответ 3

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

например. с Specflow:

Given configuration file not exist
When something
Then foo

Given configuration file exists
And some dll not exists
When something
Then bar

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

Given some scenario
| FileName         |
| a.config         |
| b.invalid.config |
When something
Then foobar

Ответ 4

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

  • Я считаю, что вам не нужно проверять реальную структуру файлов. Службы доступа к файлам определяются системой/каркасом, и их не нужно тестировать. Вам нужно высмеять эти службы в связанных тестах.
  • Также вам не нужно тестировать MEF. Он уже протестирован.
  • Используйте SOLID принципы, чтобы выполнить модульные тесты. Особенно рассмотрим принцип единой ответственности, это позволит вам создавать модульные тесты, которые не будут связаны с друг друга. Просто не забывайте насмехаться, чтобы избежать зависимостей.
  • Чтобы выполнить интеграционные тесты, вы можете создать набор вспомогательных классов, которые будут эмулировать сценарии файловых структур, которые вы хотите протестировать. Это позволит вам оставаться не прикрепленным к машине, на которой вы будете запускать эти тесты. Такой подход может быть более сложным, чем создание реальной файловой структуры, но мне это нравится.

Ответ 5

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

Попробуйте перечислить все границы проблемного домена. Если их слишком много, подумайте о том, что ваша проблема слишком широко определена и должна быть разбита. Каков полный набор необходимых и достаточных условий, необходимых для того, чтобы ваша система прошла все тесты? Затем посмотрите на каждое условие и рассматривайте его как отдельную точку атаки. И перечислите все способы, о которых вы можете думать, об их нарушении. Постарайтесь доказать себе, что вы их нашли. Затем напишите тест для каждого.

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