fbpx
devstyle.pl - Blog dla każdego programisty
devstyle.pl - Blog dla każdego programisty
3 minut

Model-View-Controller i testy jednostkowe


26.02.2008

Jak powszechnie wiadomo – wielką zaletą wzorca MVC jest umożliwienie testowania jednostkowego logiki “wyciągniętej” z klas odpowiedzialnych za interakcję z użytkownikiem. Swego czasu śledziłem w internecie dyskusje na temat “Jak testować kontrolery, aby możliwie najbardziej odizolować je od reszty aplikacji”. O to przecież chodzi w Unit Testing…

Do rzeczy.

Przedstawienie problemu

Kontroler pełni rolę pośrednika pomiędzy widokiem i modelem (View & Model). Najprostszy przykład:

 1:   public class FooController : ControllerBase 
 2:   {
 3:     IFooView _view; 
 4:     FooModel _model; 
 5:   
 6:     public FooController(IFooView view) 
 7:     { 
 8:       _view = view; 
 9:       _model = new FooModel(); 
 10:     }

Jeżeli chodzi o normalne wykorzystanie tej klasy to wszystko jest ok. Zgodnie z założeniami wzorca widok tworzy instancję kontrolera, przekazując mu jednocześnie referencję do samego siebie. I niczym się dalej nie przejmując.

Pisząc testy jednostkowe można jednak odczuć pewien niedosyt. Biblioteka testowa powinna mieć możliwość pełniejszej konfiguracji kontrolera. Co zatem robimy? Dodajemy kolejny konstruktor, co wyposaży nas w moc full-zastosowania wzorca o jakże dumnie brzmiącej nazwie Dependency Injection.Teraz nasz kontroler wygląda tak:

 1:   public FooController(IFooView view)
 2:     : this(view, new FooModel()) 
 3:   { 
 4:   } 
 5:   
 6:   public FooController(IFooView view, FooModel model) 
 7:   { 
 8:     _view = view; 
 9:     _model = model; 
 10:   }

Super – na tym etapie możemy karmić kontroler czym nam się żywnie podoba. Pojawia się jednak nowy problem: właśnie umożliwiliśmy klasom widoków robienie tego samego! Oczywiście można opatrzyć drugi konstruktor odpowiednim komentarzem (“FOR TESTING PURPOSES ONLY!!!”), ale czy nie lepiej całkiem wyeliminować taką ewentualność?

Moja propozycja

Wykorzystajmy atrybut InternalsVisibleTo. Jego zadanie to udostępnienie konkretnym – i tylko tym – bibliotekom elementów z modyfikatorem widoczności internal. Po kolei:

1) oznaczamy kłopotliwy konstruktor jako internal, ukrywając go w ten sposób przed bibliotekami widoków

internal FooController(IFooView view, FooModel model)

2) bibliotekę zawierającą kontrolery ozdabiamy odpowiednim atrybutem “zaprzyjaźniając” ją tym samym z dllką zawierającą testy:

AssemblyInfo.cs

[assembly: InternalsVisibleTo("Controllers.Tests")]

3) w testach wykorzystujemy ów pełny konstruktor, podczas gdy dla widoków jest on nadal niedostępny:

FooController controller = new FooController(mockView, mockModel);

Finito.

Nie przegap kolejnych postów!

Dołącz do ponad 9000 programistów w devstyle newsletter!

Tym samym wyrażasz zgodę na otrzymanie informacji marketingowych z devstyle.pl (doh...). Powered by ConvertKit
Notify of
Funky
Funky

Moim zdaniem widok nie powinnien inicjalizować kontrolera, dlatego, że doprowadzi to do pewnych komplikacji w skalowalności systemu. Moim zdaniem to Application Controller powinien być odpowiedzialny za uruchomienie Controllera, zaś widok będzie utworzony w ramach inicijalizacji Application Shell. Stworzenie takiego rozwiązania może być problematyczne w przydaku aplikacji ASP.NET, gdzie nie zmieniając HttpHandlera nie jesteśmy w stanie zmienić przepiegu tworzenia obiektów(ratunkiem tutaj jest ASP.NET MVC lub MonoRail). W przypdaku aplikacji ASP.NET warto spojrzeć na Web Client Software Factory, gdzie HttpModule jest odpowiedzialny za tworzenie Controllera(Presentera). Takie podejście umożliwi nam wyseperowanie odpowiedzialności tworzenia obiektów(Controllera) z poziomu widoku. W celu tworzenia obiektów warto wykorzystać… Read more »

Moja książka

Facebook

Zobacz również