MVC для тестирования

Разработка тестируемых MVC-приложений ASP.NET:

Джастин Этеридж (Justin Etheredge)

В этой статье обсуждаются:

  • ASP.NET MVC
  • Построение тестируемыми решения
  • Введение зависимостей.
В данной статье использованы следующие технологии:
приложение для ASP.NET

Содержание

Тестирование поддержки XML (веб-службы) MVC
Инструментарий
Сценарий приложения
Высокоуровневая разработка приложений
Абстрагирование модели
Шаблон хранилища
Добавление в хранилище
Реализация слой служб
Изоляция логики представления
Тестирование маршруты
Передача зависимости действий в качестве параметров
Проверка результатов действия
Подведение итогов

Вероятно, слышали, старый adage, "лучше, быстрее, дешевле, подбор любых двух." Если требуется нечто хорошо и быстрое, он не будет дешевое и если требуется быстро и дешевое, он не будет очень хорошо. Дешевле, быстрее и лучше означает, что нам необходимо написать код, несколько быстрее, справа? Если только он был просто. Изучения введите быстрее может удовлетворять два требования, но он не собираетесь сделать все лучше при разработке программного обеспечения. Так как мы сделать программное обеспечение лучше? Что делает «лучше»Среднее значение?

"Повышение"означает, что производство гибкой и поддержки программного обеспечения небольшое количество дефектов;лучшее программное обеспечение — все о долгосрочные поддерживаемость. Для достижения этого ключа решение — обеспечить, компоненты слабо связаны. Слабосвязанной программное обеспечение имеет множество преимуществ. Один, соответствующий требуемому — он улучшает наши возможность тестировать решения. Если мы записи могут быть подразделены легко на небольших частей программного обеспечения упрощается тестирования. Когда вы word его таким образом, но объем программного обеспечения используется сегодня то трудно тестирования или поддерживать показывает, не так просто, как мы может представить, звуки простой. Программное обеспечение требует сочетании полезно никаких действий, но разработчикам средств и методик для уменьшения увязки так, чтобы решения легче проверить.

Тестирование поддержки XML (веб-службы) MVC

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

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

Некоторые бы сказал, что по своей природе более можно протестировать, чем шаблон контроллера страницы (шаблон, применяемый XML (веб-службы) Web) шаблона MVC (модель вид контроллера) так как способствует разделение логику потока приложения и логику отображения. В шаблона MVC логику, которая управляет потоком приложения находится внутри классов контроллера, разработчик может легко создать и выполнить эту логику, как будто любым другим классом .NET. Это позволяет разработчикам легко выполнить бизнес-логики с помощью простого модульного теста в таким же образом бы тестирование любого другого класса POCO (обычные старые CLR Object).

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

В традиционных XML (веб-службы), одним из препятствия, разработчики, полученные во время тестирования является изобилие статических классов, используемых во время каждого запроса. Группа разработчиков XML (веб-службы) MVC внесла решение перенос множества статические вспомогательные классы .NET (такие как HttpContext и HttpRequest), могут быть заменены во время тестирования заглушку. MVC XML (веб-службы) предоставляет многие абстракции, чтобы помочь разработчикам следует избегать использования этих классов, но в местах, где вы необходимы для их использования, оболочки упростить этот код теста.

Хотя ASP.NET MVC предоставляет многие разработчики средств необходимо создать пригодные для тестирования приложения, нельзя полагаться на нем помогут в правильном направлении. Вместо этого необходимо создавать приложения специально для поддержки тестирования и несколько дополнительных средств помочь в этом:

  • Структура тестирования модулей. xUnit.NET, Wilson Михаил Newkirk таблицы. xUnit позволяет выполнять автоматизированные модульные тесты. Многие люди используют MSTest, но извлечение есть много других модулей платформ тестирования, и я рекомендую взглянуть на некоторые из них. Я выбрал xUnit.NET, так как она простой, легко расширить и имеет очень чистый синтаксис. Использую runner теста xUnit Resharper, но осталась runner теста xUnit графического ИНТЕРФЕЙСА в папке Tools образца приложения
  • Framework введения зависимостей. Ninject 2 по Николай Kohari. Ninject позволяет соединять классов в приложении. Этот подход более подробно далее в этой статье будут описаны.
  • Макетирование Framework. Moq, Clarius Consulting. Moq предоставляет платформу для макетирования интерфейсы и классы во время тестирования.

Сценарий приложения

Для демонстрации способов разработки можно протестировать приложения, я буду использовать сценарии простого приложения какие списков категорий и продуктов отображаются на веб-странице. Приложение также включает простой экранов для просмотра, редактирования и создания категорий и продуктов. Простой схема, показанный на рис. 1 состоит сопоставления один ко многим» между категорий и продуктов.

fig01.gif

Рисунок 1 схемы для образца приложения (Щелкните изображение, чтобы увеличить)

Высокоуровневая разработка приложений

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

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

Страницы кода программной части XML (веб-службы) MVC

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

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

Модель представляет серверной части приложения. Эта часть приложения, который определен в MVC XML (веб-службы) реализацией шаблона, очень слабо может занять много разных формах и его очень вероятно будет зависеть от области приложения. В небольшой приложении с немного сложной бизнес-логики например, этот уровень может просто быть набор бизнес-объектов с помощью шаблона Active записи, взаимодействовать непосредственно в слое контроллера.

Контроллер orchestrates поток приложения получение данных из модели и передавая соответствующее представление. Поскольку этот класс является отдельно от логики отображения, с применением нескольких методов вы сможете для создания экземпляра этого класса в модульный тест и не имеют зависимости от среды выполнения ASP.NET. Это позволяет завершения тестирования контроллер без необходимости выполнения внутри веб-сервера.

Абстрагирование модели

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

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formCollection)
{
    Category category = Category.FindById(id);
    category.Name = formCollection["name"];
    category.Save();            
    return RedirectToAction("List");
}

Здесь проблемы по крайней мере двоякое. Во-первых нельзя легко проверить этот код без необходимости подключения к базе данных. Поскольку используется шаблон Active записи, наш код доступа данных сочетании сущности домена, поэтому мы не может легко заменить его. Кроме того при наличии бизнес-логики, не полностью содержащихся в сущности домена, можно начать leaking бизнеса или логики приложения в класс контроллера. Это может привести к дублированию кода или даже хуже — ошибки, корневой форме от несоответствия в реализации. Это может снизить пригодности для тестирования, поскольку теперь нужно убедиться, что такое же поведение при тестировании в нескольких местах в приложении.

Шаблон хранилища

Следующим шагом улучшение пригодности для тестирования этой конструкции является использовать шаблон хранилища, который подробные, Мартина Fowler в своей книге шаблоны из Корпоративная архитектура приложений. Шаблон хранилище часто ошибочно как слой располагается между приложением и базой данных, но на самом деле это слой, который располагается между приложением и любого рода постоянного хранения. Этот шаблон confines всех данных доступ к несколько ключевых местах, позволяет удалить его для упрощения тестирования логики приложения в изоляции.

Реализации шаблона хранилища предоставляют представление на постоянное хранение видимому хранилище в памяти. Интерфейс таких хранилище может быть следующим образом:

public interface ICategoryRepository
{
    IEnumerable<Category> FindAll();
    void Insert(Category category);
    Category FindById(int id);
    void Update(Category category);
}

Тестирование в изоляции

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

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

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

Добавление в хранилище

Код на рис. 2 выглядит очистки, но как вы замените класс репозитория во время тестирования? Это просто: Вместо создания экземпляра класса в методе, он передается в классе. В этом случае можно передавать его в качестве параметра конструктора. Проблемы, однако является не управления экземпляров классов контроллера. Чтобы решить эту проблему, можно использовать структуру введения зависимостей, я ссылается ранее. Многочисленные платформы доступны на рынке. Решил использовать Ninject 2. (Если вы не знакомы с введением зависимостей, см. статью в выпуске сентябрь 2005 г. MSDN Magazine по Гриффин Caprio "введения зависимостей". Как БЫЛО сказано, первым шагом с помощью введения зависимостей является передавать CategoryRepository контроллер через конструктор на контроллер:

public CategoryController(ICategoryRepository categoryRepository)
{
    this.categoryRespository = categoryRepository;
}

На рисунке 2 усиление хранилища

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formCollection)
{
    var categoryRepository = new CategoryRepository();
    Category category = categoryRepository.FindById(id);
    category.Name = formCollection["name"];
    categoryRepository.Update(category);

    return RedirectToAction("List");
}

Далее необходимо сообщить Ninject, когда он видит интерфейс ICategoryService, его необходимо внедрить экземпляр CategoryService. Каждый framework введения зависимостей другой метод настройки и Ninject использует настройки на основе кода вместо более обычного XML. Так как эта привязка является очень просто, код для реализации его является только одну строку:

Bind<ICategoryRepository>().To<CategoryRepository>().InTransientScope();

Эта инструкция сообщает платформы введения зависимостей для привязки в временной области означает, что каждый раз при запросе ICategoryRepository появляется новый экземпляр класса CategoryRepository CategoryRepository ICategoryRepository. Это в отличие от примерно области одноэлементный продолжить получить обратно же экземпляр базы данных.

Контроллер, оптимизации, хранилище передается в конструктор можно проверить поведение контроллер независимо от базы данных. Одним из способов добиться этого является путем написания фиктивный класса (часто называемый заглушку), который реализует интерфейс ICategoryRepository. Затем можно реализовать методы, необходимо возвращать фиктивные данные и фиктивный операции, хотя это кажется массу работы. Кроме того что происходит при необходимости возвращать несколько наборов образцы данных для одного метода в различных тестов? Вы бы итоге несколько реализаций заглушки или флаги заглушки и, может быть значительное количество дополнительных усилий.

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

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

В коде рис. 3 показано теста фиктивных интерфейса ICategoryRepository, задействует метод FindAll так, что возвращает пустой список категорий, а затем передает макеты экземпляр класс контроллера. Затем вызывается метод список на класс контроллера и линия высокого уровня результат. В этом случае проверка утверждении, категории из IEnumerable, модели и что существует одну категорию в списке.

На рис. 3 список действие теста с помощью макетов

[Fact]
public void ListActionReturnsListOfCategories()
{
    // Arrange

    // create the mock
    var mockRepository = new Mock<ICategoryRepository>();

    // create a list of categories to return
    var categories = new[] { new Category {Id = 1, Name = "test"} };

    // tell the mock that when FindAll is called,
    // return the list of categories
    mockRepository.Setup(cr => cr.FindAll()).Returns(categories);

    // pass the mocked instance, not the mock itself, to the category
    // controller using the Object property
    var controller = new CategoryController(mockRepository.Object);

    // Act
    var result = (ViewResult) controller.List();

    // Assert
    var listCategories = Assert.IsAssignableFrom<IEnumerable<Category>>(result.ViewData.Model);
    Assert.Equal(1, listCategories.Count());            
}

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

Подробные служб

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

Реализация слой служб

Слой службы располагается в верхней части модели домена приложения и предоставляет набор операций, которые могут быть выполнены по нему. Это дает место для централизации логику, которая принадлежит в приложении, но может не обязательно принадлежат внутри модели домена — логику, которая будет в противном случае скорее всего утечка в методы на контроллер. Например что делать, если требуется выполнить проверку безопасности вокруг кода в рис. 2? Нежелательно операцию в методе действие (хотя может в определенных обстоятельствах) так как повторное использование этого действия в другом месте бы требуют необходимо перевести безопасности вместе с ним. Или, что делать, если требуется поместить транзакции вокруг операции? Определенно не требуется логики класс контроллера.

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

public interface ICategoryService
{
    IEnumerable<Category> FindAll();
    Category FindById(int id);
    void Save(Category category);
}

Для первых двух методов в службе делегировать вызовы хранилище и возвращаются результаты, но можно заметить, что службы имеют метод Save вместо методов Update и DELETE на хранилище. После приложение для определения, была ли объект сохранен, решение для вызова обновления или вставки в хранилище могут остаться вверх к службе.

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

Конфигурации внедрения всего зависимостей (включая репозиторий продуктов и службы) выглядит как рис. 4.

Настройка введения зависимостей на рис. 4

public class DefaultModule: NinjectModule
{
    public override void Load()
    {
        Bind<ICategoryService>().To<CategoryService>().InTransientScope();
        Bind<ICategoryRepository>().To<CategoryRepository>().InTransientScope();

        Bind<IProductService>().To<ProductService>().InTransientScope();
        Bind<IProductRepository>().To<ProductRepository>().InTransientScope();
    }
}

Мы передается хранилищ и служб через конструкторы, необходимо создать с помощью платформы введения зависимостей контроллеров. Платформа введения зависимостей обрабатывает конструкции и введения зависимостей, но необходимо запросить класс контроллера от платформы. Это гораздо проще, чем может предвидеть из-за команде XML (веб-службы) MVC контроллер фабрики подключаемых. Необходимо просто реализовать фабрики контроллеров в рис. 5 и можно затем легко заменить экземпляра по умолчанию классы контроллера.

На рис. 5 изготовителя контроллера Ninject

public class NinjectControllerFactory : DefaultControllerFactory
{
    private readonly IKernel kernel;

    public NinjectControllerFactory(IKernel kernel)
    {
        this.kernel = kernel;
    }

    protected override IController GetControllerInstance(Type controllerType)
    {
        return (IController)kernel.Get(controllerType);
    }
}

Поскольку фабрика контроллер наследует реализация по умолчанию, необходимо переопределить метод GetControllerInstance и запросить тип из ядра Ninject, который является классом, управляет создания экземпляра объекта в Ninject. Когда ядро получает тип контроллера, Ninject self-binds (пытается построить) тип, так как он является типом, конкретные. Если ядро получает тип CategoryController, конструктор будет иметь параметр типа ICategoryService. Ninject проверяет наличие у привязки для этого типа, и когда он находит тип, он выполняет те же действия ищет конструктор. Ninject ядра создает CategoryRepository и передает его конструктору для ControllerService. Затем он передает объект ControllerService конструктору для CategoryController. Все это происходит внутри контейнера внедрения зависимостей просто, запрашивая для типа.

Фабрика контроллер должен быть зарегистрирован для работы. Регистрация требует добавления только одной строки в файл global.asax:

public void Application_Start()
{
    // get the Ninject kernel for resolving dependencies
    kernel = CreateKernel();

    // set controller factory for instantiating controller and injecting dependencies
    ControllerBuilder.Current.SetControllerFactory(new NinjectController Factory(kernel));
}

Теперь когда пользователь запрашивает URL-адрес и XML (веб-службы) MVC пытается создать контроллером, он фактически заканчивается, создаваемого контейнера внедрения зависимостей. При тестировании на контроллер может макетируйте службу для проверки на контроллере в изоляции, как показано на рис. 6.

Тестирование действия список макеты службой на рисунке 6

[Fact]
public void ListActionReturnsListOfCategories()
{
    // Arrange
    var mockService = new Mock<ICategoryService>();
    var categories = new[] { new Category { Id = 1, Name = "test" } };
    mockService.Setup(cr => cr.FindAll()).Returns(categories);
    var controller = new CategoryController(mockService.Object);

    // Act
    var result = (ViewResult) controller.List();

    // Assert
    var listCategories = Assert.IsAssignableFrom<IEnumerable<Category>>(result.ViewData.Model);            
    Assert.Equal(1, listCategories.Count());
}

Изоляция логики представления

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

<%= Html.Encode(String.Format("{0:c}", this.Model.Price)) %>

Я лично не нравится этот подход, поскольку я не удается легко проведение модульные тесты на эту логику, при форматировании поле в представлении. (я не против с помощью пользовательского ИНТЕРФЕЙСА тестирования платформы, но требуется получить столько Мой тестирования как можно сделать в моей модульных тестов). Одним из решений является добавление свойства в классе продукта, чтобы форматировать цену для отображения, но МНЕ не нравится это, поскольку означает запуск вопросы Мое представление для утечка в слой домена. Что-нибудь простой формат цены не может показаться уж страшно, но проблемы всегда кажутся увеличить приложение увеличивается в размере, а небольших элементов начать вызвать проблемы. Например что произойдет, если необходимо по-разному отобразить цену на двух страницах? Запустится Добавление перегрузки для каждой версии разные объект домена?

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

Если применять модель презентации были предыдущей инструкции вместо бы выглядеть следующим образом:

<%= Html.Encode(this.Model.DisplayPrice) %>

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

На рис. 7 модульного теста для метода

[Fact]
public void PriceIsFormattedAsCurrency()
{
    // Arrange
    var product = new Product {Price = 9.22m};
    var presentationProduct = new 
        PresentationProductStub(product);

    // Act
    string cost = presentationProduct.DisplayPrice;

    // Assert
    Assert.Equal("$9.22", cost);
}

Класс PresentationProductStub, происходящего от класса PresentationProductBase, который сопоставляет с класс продукта и. Это вызывает действие список для класса ProductController выглядеть следующим образом:

public ActionResult List()
{
    var products = productService.FindAll();
    return View(new ProductList().MapList(products));
}

Продукты выбираются из службы обычно, но перед отправкой в представление они передаются в метод класса ProductList, который преобразует список классов продукта в список классов ProductList. С помощью модели для ваших представлений имеет несколько преимуществ различных. Во-первых как вы уже видели, можно добавить поведение конкретного представления к классу, которые легко могут быть модульного тестирования. Это удобно для форматирования или transforming данные для отображения. Во-вторых можно использовать подшивки модели по умолчанию, встроенные в ASP.NET MVC и поместить модель презентации в список параметров метода POST, как показано ниже:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(ProductCreate productCreate)
{            
    productService.Save(productCreate.GetProduct());
    return RedirectToAction("List");            
}

Если вы не учитывать как работает модель связыватель по умолчанию XML (веб-службы) MVC, просто сопоставляет поля в HTML по имени на свойства класса в списке параметров. Если в списке параметров в предыдущем коде я поместить класс продукта, пользователь может отправить злоумышленником часть данных с именем "Идентификатор"и заменить ключ на модели. Это не очевидно, что хорошо. Аналогично пользователь может сопоставлять имена любое поле Мой класс и перезаписать значения. С помощью модели на представления позволяет использовать подшивки модели по умолчанию (Если выбрано) во время остается возможность контролировать, какие свойства сопоставляются объекта домена.

Альтернативным вариантом является определять классы сопоставления, которые можно использовать для сопоставления сущностей, поступающие через параметры метода действие для сущностей, который требуется сохранить в базе данных. Это позволяет управлять какие свойства для копирования перед сохранением в базе данных. Другим вариантом является создание инструмента сопоставления на основе отражения, который можно использовать для сопоставления полей, аналогичны созданные Jimmy Bogard AutoMapper средство.

Тестирование маршруты

Теперь со многими высокоуровневых архитектурных советы для тестирования, так давайте сосредоточиться на более детального тестирования подходов. Реализация приложения XML (веб-службы) MVC первым делом для написания тестов проверить поведение маршруты в приложении. Важно, чтобы убедиться, что базовый маршрута "~ /»будет пересылать соответствующий контроллер и действие и других контроллеров и действия переслать правильно также. Очень важно иметь эти тесты в месте с самого начала, чтобы позже, дополнительные маршруты добавляются в приложение, вы гарантируется, что маршруты, которые уже были определены не являются неработающие.

Начните определение теста по умолчанию маршрута (см. рис. 8). В этом приложении контроллера по умолчанию определяется как "Категория"контроллер и действие по умолчанию в этот контроллер имеет значение «список». Наши теста необходимо проверить базового маршрута и assert правильные значения содержащиеся в данных маршрута.

На рис. 8 тестирование данных маршрутизации

[Fact]
public void DefaultUrlRoutesToCategoryControllerAndListAction()
{
    // Arrange
    var context = ContextHelper.GetMockHttpContext("~/");
    var routes = new RouteCollection();
    MvcApplication.RegisterRoutes(routes);

    // Act
    RouteData routeData = routes.GetRouteData(context.Object);

    // Assert
    RouteTestingHelper.AssertRouteData(routeData, "Category", "List", "");
}

Этот тест используется вспомогательная функция для assert значения маршрут по умолчанию, показано ниже:

public static void AssertRouteData(RouteData routeData, 
    string controller, string action, string id)
{
    Assert.NotNull(routeData);
    Assert.Equal(controller, routeData.Values["controller"]);
    Assert.Equal(action, routeData.Values["action"]);
    Assert.Equal(id, routeData.Values["id"]);
}

Этот же общий шаблон теста можно передать маршрут, который соответствует шаблону действие контроллер код (например, «~/Product/Edit/12)» HttpContext и assert значения (см. рис. 9).

Проверка данных маршрута с помощью поддержки на рисунке 9

[Fact]
public void ProductEditUrlRoutesToProductControllerAndListAction()
{
    // Arrange
    var context = ContextHelper.GetMockHttpContext("~/Product/Edit/12");
    var routes = new RouteCollection();
    MvcApplication.RegisterRoutes(routes);

    // Act
    RouteData routeData = routes.GetRouteData(context.Object);

    // Assert
    RouteTestingHelper.AssertRouteData(routeData, "Product", "Edit", "12");
}

С помощью этого метода тесты для маршрутов являются немного verbose (для ясности обучения), и они наверняка может быть сокращена. Борис Scheirman выполнила некоторые интересные работы в этой области, и он имеет хорошо сообщение в своем блоге внесение очень насыщенный маршрута утверждения ("Fluent тестирование маршрута в MVC XML (веб-службы)").

Передача зависимости действий в качестве параметров

Второй совет является передать все зависимости контроллер действий в качестве параметров методу действие. Я иллюстрируют пример загрузки файла в образце приложения. Как упоминалось ранее, HttpRequest в XML (веб-службы) среды выполнения является статическим классом, чрезвычайно трудно заменить или макетируйте во время тестирования модулей. В XML (веб-службы) MVC Чтобы разрешить эти классы быть макетированным предоставляются классы-оболочки, но процесс их макетирования или stubbing out по-прежнему может быть сложным.

Чтобы макетируйте HttpRequestBase объекта необходимо макетируйте HttpContextBase объект, который располагается и затем заполните несколько свойств для каждого. Для сохранения файла проблемы необходимо макетируйте размещена на HttpContextBase HttpServerUtilityBase. Вместо создания нескольких макеты объектов для имитации различных единиц HttpContextBase было бы неплохо просто сделать HttpServerUtilityBase параметра метода действие и затем передать в одной макеты класса во время тестирования:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, ViewProduct viewProduct, 
    HttpServerUtilityBase server, HttpPostedFileBase imageFile)
{

Заметить, что третий параметр в предыдущем коде тип, который необходимо использовать и тип, нам нужно макетируйте. С подписью метода следующим образом необходимо только макетируйте класса HttpServerUtilityBase. Если мы доступ к этого класса через this.HttpContext.Server, его было бы необходимо макетируйте также класс HttpContextBase. Проблема в том что только Добавление класса HttpServerUtilityBase, параметры метода не сделать ее работы;необходимо указать способ сообщить XML (веб-службы) MVC, как создать экземпляр этого класса. Это источник подшивки модель платформы. Обратите внимание, что HttpPostedFileBase уже подшивки пользовательской модели, присвоенных ему по умолчанию.

Модель подшивки являются классами, реализующие интерфейс IModelBinder и предоставляет способ XML (веб-службы) MVC для создания экземпляров типов на действия методов. В этом случае необходимы модели подшивки для HttpServerUtilityBase. Мы можем создать класс, который выглядит следующим образом:

public class HttpServerModelBinder: IModelBinder
{
    public object BindModel(ControllerContext controllerContext, 
        ModelBindingContext bindingContext)
    {
        return controllerContext.HttpContext.Server;
    }
}

Связыватель модель имеет доступ к контекст на контроллере и контекста привязки. Подшивки модели просто обращается к контекст на контроллере и возвращает HttpServerUtilityBase класс от класса HttpContext. Все, что остается является сообщить среда XML (веб-службы) MVC для использования этой модели подшивки при обнаружении параметра типа HttpServerUtilityBase. Обратите внимание, что этот код размещается в файле Global.asax, так как:

public void Application_Start()
{
    // assign model binder for passing in the 
    // HttpServerUtilityBase to controller actions
    ModelBinders.Binders.Add(typeof(HttpServerUtilityBase), new HttpServerModelBinder());
}

Теперь при HttpServerUtilityBase передается в качестве параметра методу действие, среда выполнения XML (веб-службы) MVC вызывает метод BindModel HttpServerModelBinder, чтобы получить экземпляр класса. Очень просто создать модель подшивки, и они сделать тестирования контроллер действий гораздо проще.

Проверка результатов действия

Одной из ранних жалобы о XML (веб-службы) MVC точки зрения сложности тестирования операций, таких как представление было визуализируемого из метода действие. Пришлось макетируйте контроллер контекст, обработчик просмотра и т.д. Он был действительно кошмарно. В режиме предварительного просмотра 3 группа XML (веб-службы) MVC добавила концепцию результатов действия, которые являются классами, полученные из класса ActionResult и представляют задачу, которая собирается выполнить действие. Теперь в XML (веб-службы) MVC каждого действия метода возвращает тип ActionResult, и разработчик можно выбрать число типов встроенных результат действия или создать свои собственные. Это помогает при тестировании, поскольку вызов метода действие и проверить результат, чтобы увидеть, что произошло. Рассмотрим пример ViewResult, мы можно создавать путем вызова метода представления в базовом классе контроллера, как показано ниже:

public ActionResult List()
{
    IEnumerable<Category> categories = categoryService.FindAll();
    return View(ViewCategory.MapList(categories));
}

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

Результаты тестирования действие на рис. 10

[Fact]
public void ListActionReturnsViewResultWithDefaultName()
{
    // Arrange
    var mockService = new Mock<ICategoryService>();
    var controller = new CategoryController(mockService.Object);

    // Act
    var result = controller.List();

    // Assert
    var viewResult = Assert.IsType<ViewResult>(result);            
    Assert.Empty(viewResult.ViewName);
}

Этот тест фиктивных CategoryService, чтобы создать класс контроллера.Затем тест вызывает действие список для получения объекта ActionResult.Тест наконец утверждает, что результат имеет тип ViewResult, и пустое имя представления.Было бы очень легко проверить имя представления от некоторых известных строковое значение или в случаях где возвращаемый тип является что-нибудь немного более сложный (JSON формат, например), проверяемое свойство данных на результат, убедитесь, что он содержит ожидаемые данные как возвращенный вызовом метода действие.

Рекомендуемая литература

  • Гибкая принципы, шаблоны и методики в C# по Роберт C.Мартин и Мартина Micah (Prentice зал, 2006 г.)
  • Шаблоны корпоративной архитектуры приложения по Мартина Fowler (Addison-Wesley Professional, 2002 г.)
  • Microsoft .NET: Создание архитектуры приложений для предприятия Дино Эспозито, Анна Saltarello (Microsoft Press, 2008)

Подведение итогов

Группа XML (веб-службы) MVC бумаги значительную усилия в создании гибкую архитектуру, которая позволяет легко тестирования.Разработчики могут теперь более легко проверить свои системы из-за возможностей, таких как подключаемые контроллер фабрики, типы результата действия и оболочки вокруг XML (веб-службы) типов контекста.Все это предоставляет разработчикам XML (веб-службы) MVC хорошей отправной точкой, но по-прежнему на разработчикам проектировать и создавать приложения, которые слабо связанной и пригодные для тестирования является onus.Надеюсь, что статья поможет как ход работы этот путь.Если вы заинтересованы, я перечислены в боковой панели чтения рекомендуется.

Джастин Etheredge является Microsoft C# MVP, автором адресу CodeThinked.com и основатель группы мастерства Richmond программного обеспечения.Он — старший консультант в Dominion цифровой Richmond, Вирджиния, он предоставляет руководство в разработке и построении систем размеров и форм.