MVC 3: Как научиться тестировать с помощью NUnit, Ninject и Moq? - PullRequest
48 голосов
/ 11 июля 2011

Краткая версия моих вопросов:

  1. Может ли кто-нибудь указать мне на некоторые хорошие, подробные источники, из которых я можно узнать, как реализовать тестирование в моем приложении MVC 3, используя NUnit, Ninject 2 и Moq?
  2. Может кто-нибудь здесь помочь объяснить мне, как контроллер-репозиторий развязка, насмешка и внедрение зависимостей работают вместе?
* * 1010

Более длинная версия моих вопросов:

Что я пытаюсь сделать ...

В настоящее время я начинаю создавать приложение MVC 3, которое будет использовать Entity Framework 4, с подходом, основанным на базе данных. Я хочу сделать это правильно, поэтому я пытаюсь спроектировать классы, слои и т. Д., Чтобы они были хорошо тестируемыми. Но у меня практически нет опыта модульного тестирования или интеграционного тестирования, кроме академического понимания их.

После долгих исследований я остановился на использовании

  • NUnit как моя структура тестирования
  • Ninject 2 как моя структура внедрения зависимостей
  • Moq как мой насмешливый каркас.

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

Что я узнал до сих пор ...

Я потратил некоторое время, работая над некоторыми из этих вещей, читая такие ресурсы, как:

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

Там, где мне стало плохо ...

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

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

Подводя итог всему этому, сводя его к конкретным вопросам, мне интересно следующее:

  1. Может кто-нибудь указать мне на некоторые хорошие, подробные источники, из которых я можно узнать, как реализовать тестирование в моем приложении MVC 3, используя NUnit, Ninject 2 и Moq?
  2. Может кто-нибудь здесь помочь объяснить мне, как Controller-Repository развязка, насмешка и внедрение зависимостей работают вместе?

РЕДАКТИРОВАТЬ:

Я только что открыл официальную вики Ninject на Github, так что я собираюсь начать работать над этим, чтобы посмотреть, не станет ли это проясняться для меня. Но меня все еще очень интересуют мысли сообщества SO по поводу всего этого:)

Ответы [ 3 ]

60 голосов
/ 11 июля 2011

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

После установки этого пакета он создаст файл NinjectMVC3.cs в папке App_Start, внутри этого класса есть метод RegisterServices,Здесь вы должны создать привязки между вашими интерфейсами и вашими реализациями

private static void RegisterServices(IKernel kernel)  
{  
  kernel.Bind<IRepository>().To<MyRepositoryImpl>();
  kernel.Bind<IWebData>().To<MyWebDAtaImpl>();
}        

Теперь в вашем контроллере вы можете использовать инжектор конструктора.

public class HomeController : Controller {  
    private readonly IRepository _Repo;
    private readonly IWebData _WebData;

    public HomeController(IRepository repo, IWebData webData) {
      _Repo = repo;
      _WebData = webData;
    }
}

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

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

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

bool TryCreateUser(string username);

, который вызывается методом контроллера

public ActionResult CreateUser(string username) {
    if (_Repo.TryCreateUser(username))
       return RedirectToAction("CreatedUser");
    else
       return RedirectToAction("Error");
}

То, что вы действительно пытаетесь проверить здесь, это оператор if и возвращаемый результаттипы, вы не хотите создавать реальный репозиторий, который будет возвращать true или false на основе специальных значений, которые вы ему даете.Это то место, где вы хотите издеваться.

public void TestCreateUserSucceeds() {
    var repo = new Mock<IRepository>();
    repo.Setup(d=> d.TryCreateUser(It.IsAny<string>())).Returns(true);
    var controller = new HomeController(repo);
    var result = controller.CreateUser("test");
    Assert.IsNotNull(result);
    Assert.IsOfType<RedirectToActionResult>(result)
    Assert.AreEqual("CreatedUser", ((RedirectToActionResult)result).RouteData["Action"]);
}

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

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

Последний совет, специфичный для MVC, в любое время, когда вам нужен доступ к основным веб-объектам,HttpContext, HttpRequest и т. Д., Также оберните все это за интерфейс (как, например, IWebData в моем примере), потому что, хотя вы можете имитировать их, используя * Base классы, это становится очень болезненным, так как они имеют много внутренних зависимостей, вам также необходимоmock.
Также с Moq, установите MockBehaviour на Strict при создании макетов, и он сообщит вам, если что-то вызывается, для которого вы не предоставили макет.

9 голосов
/ 11 июля 2011
  1. Вот приложение, которое я создаю.Он с открытым исходным кодом и доступен на GitHub, и использует все необходимые вещи - MVC3, NUnit, Moq, Ninject - https://github.com/alexanderbeletsky/trackyt.net/tree/master/src

  2. Развязка Contoller-Repository проста.Все операции с данными перемещаются в сторону репозитория.Репозиторий - это реализация некоторого типа IRepository.Контроллер никогда не создает репозитории внутри себя (с помощью оператора new), а получает их либо по аргументу конструктора, либо по свойству.

.

public class HomeController {
  public HomeController (IUserRepository users) {

  }
}

Этот методназывается «инверсия контроля».Для поддержки инверсии управления вы должны предоставить некоторую платформу «Dependency Injection».Ninject хороший.Внутри Ninject вы связываете какой-то конкретный интерфейс с классом реализации:

Bind<IUserRepository>().To<UserRepository>();

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

public class TrackyControllerFactory : DefaultControllerFactory
{
    private IKernel _kernel = new StandardKernel(new TrackyServices());

    protected override IController GetControllerInstance(
        System.Web.Routing.RequestContext requestContext,
        Type controllerType)
    {
        if (controllerType == null)
        {
            return null;
        }

        return _kernel.Get(controllerType) as IController;
    }
}

Когда инфраструктура MVC собирается создать новый контроллер, вызов делегируется методу фабрики пользовательских контроллеров GetControllerInstance, который делегирует егоNinject.Ninject видит, что для создания этого контроллера у конструктора есть один аргумент типа IUserRepository.Используя объявленную привязку, он видит, что «мне нужно создать UserRepository для удовлетворения потребностей IUserRepository».Он создает экземпляр и передает его конструктору.

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

Примеры кода:

1 голос
/ 13 июля 2011

Проверьте: DDD Melbourne video - Новый рабочий процесс разработки

Весь процесс разработки ASP.NET MVC 3 был очень хорошо представлен.

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

  • Использование NuGet для установки Ninject для включения DI во всем MVC3 рамки
  • Использование NuGet для установки nSubstite для создания макетов для включения устройства тестирование
...