it-swarm.com.ru

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

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

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

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

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

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

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

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

54
matori82

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Не ясно, насколько вы знакомы с модульным/интеграционным тестированием. В любом случае, я бы порекомендовал 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(){  
    ...
}
8
Kjartan

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

Например. с 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
3
Sergey Berezovskiy

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

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

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

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

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

0
Brad Thomas