MVC 3: Jak nauczyć się testować za pomocą NUnit, Ninject i Moq?

Krótka wersja moich pytań:

    Czy ktoś może wskazać mi jakieś dobre, szczegółowe źródła, z których można dowiedzieć się, jak zaimplementować testowanie w mojej aplikacji MVC 3, za pomocą NUnit, Ninject 2 i Moq?
  1. Czy ktoś może mi pomóc wyjaśnić jak Controller-repozytorium odsprzęganie, wyśmiewanie i wstrzykiwanie zależności działają razem?

Dłuższa wersja moich pytań:

What I ' m trying to do ...

Obecnie zaczynam tworzyć aplikację MVC 3, która będzie używać Entity Framework 4, z podejściem bazodanowym. Chcę to zrobić dobrze, więc staram się zaprojektować klasy, warstwy itp., być wysoce testowalne. Ale mam małe lub żadne doświadczenie z testami jednostkowymi lub Integracyjnymi, poza akademickim zrozumieniem ich.

Po wielu badaniach, zdecydowałem się na użycie

  • NUnit jako mój test framework
  • Ninject 2 as my dependency injection framework
  • Moq jako mój szyderczy framework.

Znam temat który framework jest najlepszy itp., może wejść w to, ale w tym momencie naprawdę Nie wiem wystarczająco dużo o tym, aby sformułować solidną opinię. Więc zdecydowałem się na te darmowe rozwiązania, które wydają się być lubiane i dobrze utrzymane.

Tego się do tej pory nauczyłem ...

Spędziłem trochę czasu pracując przez niektóre z tych rzeczy, czytanie zasobów takich jak:

Z tych zasobów udało mi się wytrenować potrzebę Wzór repozytorium, wraz z interfejsami repozytorium, w celu oddzielenia moich kontrolerów i logiki dostępu do danych. Już trochę tego napisałem w swoim zgłoszeniu, ale przyznaję, że nie jestem do końca pewny co do mechaniki całej sprawy i czy robię to oddzielenie dla wsparcia wyśmiewania, czy też iniekcji zależności, czy obu. W związku z tym, z pewnością nie miałbym nic przeciwko usłyszeniu od Was o tym. Każda jasność, jaką mogę uzyskać w tej sprawie, pomoże mi w tym momencie.

Gdzie zrobiło się dla mnie błotnisto ...

Myślałem, że dobrze chwytam te rzeczy, dopóki nie zacząłem próbować owijać głowy wokół Ninject, jak opisano w Building Testable ASP.NET aplikacje MVC , cytowane powyżej. W szczególności całkowicie zgubiłem się w punkcie, w którym autor zaczyna opisywać implementację warstwy usług, mniej więcej w połowie dokumentu.

W każdym razie, Szukam teraz więcej środków do nauki, aby spróbować uzyskać różne / align = "left" /

Podsumowując to wszystko, skupiając się na konkretnych pytaniach, zastanawiam się co następuje:

    Czy ktoś może wskazać mi jakieś dobre, szczegółowe źródła, z których można dowiedzieć się, jak zaimplementować testowanie w mojej aplikacji MVC 3, za pomocą NUnit, Ninject 2 i Moq?
  1. Czy ktoś może mi pomóc wyjaśnić jak Controller-repozytorium praca odsprzęganie, wyśmiewanie i dependencja razem?

EDIT:

Właśnie odkryłem Ninject official wiki {[50] } na Githubie, więc zamierzam zacząć nad tym pracować, aby zobaczyć, czy to zacznie wyjaśniać rzeczy dla mnie. Ale nadal jestem bardzo zainteresowany przemyśleniami społeczności SO na ten temat:)

Author: campbelt, 2011-07-11

3 answers

Jeśli używasz Ninject.MVC3 pakiet nuget, wtedy część artykułu, który podlinkowałeś, który powodował zamieszanie, nie będzie wymagana. Ten pakiet ma wszystko, czego potrzebujesz, aby rozpocząć wstrzykiwanie kontrolerów, co jest prawdopodobnie największym punktem bólu.

Po zainstalowaniu tego pakietu, utworzy on NinjectMVC3.plik cs w folderze App_Start, wewnątrz tej klasy znajduje się metoda RegisterServices. W tym miejscu należy utworzyć powiązania między interfejsami i implementacje

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

Teraz w kontrolerze możesz użyć constructor injection.

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

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

Jeśli jesteś po bardzo wysokim zasięgu testu, to w zasadzie za każdym razem, gdy jeden logiczny kawałek kodu (powiedzmy kontroler) musi rozmawiać z innym (powiedzmy baza danych), powinieneś utworzyć interfejs i implementację, dodać powiązanie definicji do RegisterService i dodać nowy argument konstruktora.

Dotyczy to nie tylko kontrolera, ale każdej klasy, więc w powyższym przykładzie jeśli Twoje repozytorium implementacja potrzebowała do czegoś instancji WebData, dodałbyś pole readonly i konstruktor do swojej implementacji repozytorium.

Następnie, jeśli chodzi o testowanie, to co chcesz zrobić, to dostarczyć wyśmiewaną wersję wszystkich wymaganych interfejsów, tak, że jedyną rzeczą, którą testujesz, jest kod w metodzie, dla której piszesz test. Więc w moim przykładzie powiedzmy, że IRepository ma

bool TryCreateUser(string username);

Który jest wywoływany metodą kontrolera

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

What you are naprawdę próbuje przetestować tutaj jest to, że If I typy return, nie chcesz tworzyć repozytorium, które zwróci true lub false na podstawie specjalnych wartości, które podasz. Tu chcesz się kpić.

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"]);
}

^ to się nie skompiluje jak znam lepiej xUnit i nie pamiętam nazw właściwości na przekierowaniu z góry głowy.

Więc podsumowując, jeśli chcesz, aby jeden kawałek kodu rozmawiał z innym, Walnij interfejs pomiędzy. To wtedy pozwala na wyśmiewanie drugiego kawałka kodu, dzięki czemu podczas testowania pierwszego możesz kontrolować wyjście i mieć pewność, że testujesz tylko dany kod.
Myślę, że to właśnie ten punkt naprawdę sprawił, że Penny drop dla mnie z tym wszystkim, robisz to niekoniecznie dlatego, że Kod tego wymaga, ale dlatego, że testy tego wymagają.

Ostatnia rada specyficzna dla MVC, w każdej chwili musisz uzyskać dostęp do podstawowych obiektów web, HttpContext, HttpRequest itp. interfejs, jak również (jak IWebData w moim przykładzie) , ponieważ podczas gdy można mock te za pomocą klas podstawowych*, staje się bolesne bardzo szybko, ponieważ mają wiele wewnętrznych zależności, które również trzeba mock.
Również z Moq, Ustaw MockBehaviour na Strict podczas tworzenia mocks i powie ci, jeśli coś jest wywoływane, że nie podałeś mock dla.

 60
Author: Chris Sainty,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-11 06:42:51
  1. Oto aplikacja, którą tworzę. Jest open source i dostępny na GitHubie, i wykorzystuje wszystkie wymagane rzeczy-MVC3, NUnit, Moq, Ninject - https://github.com/alexanderbeletsky/trackyt.net/tree/master/src

  2. Odsprzęgnięcie Contoller-repozytorium jest proste. Wszystkie operacje na danych są przenoszone do repozytorium. Repozytorium jest implementacją typu irepository. Kontroler nigdy nie tworzy repozytoriów wewnątrz siebie (za pomocą new operator), ale raczej otrzymuje je albo przez argument konstruktora lub właściwość.

.

public class HomeController {
  public HomeController (IUserRepository users) {

  }
}
Technika ta nazywana jest " inwersją sterowania."Aby wspomóc inwersję sterowania, musisz podać jakiś framework "Dependency Injection". Ninject jest dobry. W Ninject kojarzysz jakiś konkretny interfejs z klasą implementacji:
Bind<IUserRepository>().To<UserRepository>();

Możesz również zastąpić domyślną fabrykę kontrolera swoim niestandardowym. Wewnątrz niestandardowego, który delegujesz wywołanie do jądra 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;
    }
}

Gdy infrastruktura MVC ma zamiar utworzyć nowy kontroler, wywołanie jest delegowane do niestandardowej fabrycznej metody GetControllerInstance, która deleguje go do Ninject. Ninject widzi, że aby utworzyć kontroler konstruktor ma jeden argument typu IUserRepository. Używając zadeklarowanego wiązania widzi, że " muszę stworzyć UserRepository, aby zaspokoić potrzeby IUserRepository."Tworzy instancję i przekazuje ją do konstruktor.

Konstruktor nigdy nie jest świadomy, jaka instancja zostanie przekazana wewnątrz. Wszystko zależy od wiązania, które zapewnisz.

Przykłady kodu:

 9
Author: Alexander Beletsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-10 19:26:05

Zobacz to: DDD Melbourne video - New development workflow

Cały ASP.NET bardzo dobrze zaprezentowany został proces rozwoju MVC 3.

Narzędzia stron trzecich, które najbardziej lubię to:

  • użycie NuGet do instalacji Ninject, aby włączyć DI w całym MVC3 framework
  • używanie NuGet do instalacji nSubstite do tworzenia mocks, aby włączyć unit testowanie
 1
Author: Vincent,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-13 01:59:16